Pārlūkot izejas kodu

SONAR-4898 drop RMI and autokill

tags/4.5-RC3
Simon Brandhof pirms 9 gadiem
vecāks
revīzija
bbca1b8de5
54 mainītis faili ar 605 papildinājumiem un 1423 dzēšanām
  1. 12
    27
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java
  2. 17
    24
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java
  3. 0
    44
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JmxConnector.java
  4. 9
    53
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java
  5. 0
    60
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/PingerThread.java
  6. 46
    33
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java
  7. 0
    145
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/RmiJmxConnector.java
  8. 2
    2
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/StreamGobbler.java
  9. 3
    20
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/TerminatorThread.java
  10. 0
    75
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Timeouts.java
  11. 6
    6
      server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java
  12. 0
    34
      server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/CallVerifierJmxConnector.java
  13. 0
    42
      server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/ImpossibleToConnectJmxConnector.java
  14. 2
    15
      server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java
  15. 1
    2
      server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java
  16. 10
    96
      server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java
  17. 0
    71
      server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/RmiJmxConnectorTest.java
  18. 0
    27
      server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TerminationFailureRmiConnector.java
  19. 0
    16
      server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TimeoutsTest.java
  20. 1
    1
      server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/WatcherThreadTest.java
  21. 0
    81
      server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java
  22. 4
    2
      server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java
  23. 3
    0
      server/sonar-process/src/main/java/org/sonar/process/MessageException.java
  24. 3
    2
      server/sonar-process/src/main/java/org/sonar/process/Monitored.java
  25. 10
    4
      server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java
  26. 26
    60
      server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java
  27. 0
    1
      server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java
  28. 0
    28
      server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java
  29. 9
    12
      server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java
  30. 1
    1
      server/sonar-process/src/main/java/org/sonar/process/Props.java
  31. 27
    23
      server/sonar-process/src/main/java/org/sonar/process/SharedStatus.java
  32. 0
    26
      server/sonar-process/src/main/java/org/sonar/process/State.java
  33. 10
    8
      server/sonar-process/src/main/java/org/sonar/process/StopperThread.java
  34. 0
    28
      server/sonar-process/src/main/java/org/sonar/process/Terminable.java
  35. 0
    127
      server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java
  36. 13
    0
      server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java
  37. 2
    3
      server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java
  38. 17
    52
      server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java
  39. 0
    28
      server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java
  40. 101
    0
      server/sonar-process/src/test/java/org/sonar/process/SharedStatusTest.java
  41. 79
    0
      server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java
  42. 10
    5
      server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
  43. 85
    0
      server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java
  44. 5
    5
      server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java
  45. 72
    77
      server/sonar-search/src/main/java/org/sonar/search/SearchServer.java
  46. 5
    0
      server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java
  47. 4
    4
      server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java
  48. 3
    9
      server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java
  49. 1
    2
      server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java
  50. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java
  51. 0
    9
      sonar-application/src/main/assembly/conf/sonar.properties
  52. 3
    24
      sonar-application/src/main/java/org/sonar/application/App.java
  53. 0
    4
      sonar-application/src/main/java/org/sonar/application/DefaultSettings.java
  54. 2
    4
      sonar-application/src/test/java/org/sonar/application/DefaultSettingsTest.java

+ 12
- 27
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java Parādīt failu

@@ -37,9 +37,6 @@ public class JavaCommand {

private File workDir;

// any available port by default
private int jmxPort = -1;

// for example -Xmx1G
private final List<String> javaOptions = new ArrayList<String>();

@@ -54,6 +51,8 @@ public class JavaCommand {

private final Map<String, String> envVariables = new HashMap<String, String>(System.getenv());

private File tempDir = null;

public JavaCommand(String key) {
this.key = key;
}
@@ -71,24 +70,20 @@ public class JavaCommand {
return this;
}

/**
* Shortcut to set the java option -Djava.io.tmpdir
*/
public JavaCommand setTempDir(File tempDir) {
this.javaOptions.add("-Djava.io.tmpdir=" + tempDir.getAbsolutePath());
return this;
public File getTempDir() {
return tempDir;
}

public int getJmxPort() {
return jmxPort;
public JavaCommand setTempDir(File tempDir) {
this.tempDir = tempDir;
return this;
}

/**
* Current mandatory to be set
*/
public JavaCommand setJmxPort(int jmxPort) {
this.jmxPort = jmxPort;
return this;
public File getReadyFile() {
if (tempDir == null) {
throw new IllegalStateException("Temp directory not set");
}
return new File(tempDir, key + ".ready");
}

public List<String> getJavaOptions() {
@@ -156,20 +151,10 @@ public class JavaCommand {
return this;
}

public boolean isDebugMode() {
for (String javaOption : javaOptions) {
if (javaOption.contains("-agentlib:jdwp")) {
return true;
}
}
return false;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder("JavaCommand{");
sb.append("workDir=").append(workDir);
sb.append(", jmxPort=").append(jmxPort);
sb.append(", javaOptions=").append(javaOptions);
sb.append(", className='").append(className).append('\'');
sb.append(", classpath=").append(classpath);

+ 17
- 24
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java Parādīt failu

@@ -21,9 +21,9 @@ package org.sonar.process.monitor;

import org.apache.commons.lang.StringUtils;
import org.slf4j.LoggerFactory;
import org.sonar.process.LoopbackAddress;
import org.sonar.process.ProcessEntryPoint;
import org.sonar.process.ProcessUtils;
import org.sonar.process.SharedStatus;

import java.io.File;
import java.io.FileOutputStream;
@@ -44,20 +44,27 @@ public class JavaProcessLauncher {
ProcessRef launch(JavaCommand command) {
Process process = null;
try {
// cleanup existing monitor file. Child process creates it when ready.
// TODO fail if impossible to delete
SharedStatus sharedStatus = new SharedStatus(command.getReadyFile());
sharedStatus.prepare();

ProcessBuilder processBuilder = create(command);
LoggerFactory.getLogger(getClass()).info("Launch {}: {}",
command.getKey(), StringUtils.join(processBuilder.command(), " "));

long startedAt = System.currentTimeMillis();
process = processBuilder.start();
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), command.getKey());
StreamGobbler inputGobbler = new StreamGobbler(process.getInputStream(), command.getKey());
inputGobbler.start();
errorGobbler.start();

return new ProcessRef(command.getKey(), process, errorGobbler, inputGobbler);
ProcessRef ref = new ProcessRef(command.getKey(), sharedStatus, process, inputGobbler);
ref.setLaunchedAt(startedAt);
return ref;

} catch (Exception e) {
// just in case
ProcessUtils.destroyQuietly(process);
ProcessUtils.sendKillSignal(process);
throw new IllegalStateException("Fail to launch " + command.getKey(), e);
}
}
@@ -66,17 +73,17 @@ public class JavaProcessLauncher {
List<String> commands = new ArrayList<String>();
commands.add(buildJavaPath());
commands.addAll(javaCommand.getJavaOptions());
commands.addAll(buildJmxOptions(javaCommand));
// TODO warning - does it work if temp dir contains a whitespace ?
commands.add(String.format("-Djava.io.tmpdir=%s", javaCommand.getTempDir().getAbsolutePath()));
commands.addAll(buildClasspath(javaCommand));
commands.add(javaCommand.getClassName());

// TODO warning - does it work if temp dir contains a whitespace ?
commands.add(buildPropertiesFile(javaCommand).getAbsolutePath());

ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(commands);
processBuilder.directory(javaCommand.getWorkDir());
processBuilder.environment().putAll(javaCommand.getEnvVariables());
processBuilder.redirectErrorStream(true);
return processBuilder;
}

@@ -86,18 +93,6 @@ public class JavaProcessLauncher {
"bin" + separator + "java").getAbsolutePath();
}

private List<String> buildJmxOptions(JavaCommand javaCommand) {
if (javaCommand.getJmxPort() < 1) {
throw new IllegalStateException("JMX port is not set");
}
return Arrays.asList(
"-Dcom.sun.management.jmxremote",
"-Dcom.sun.management.jmxremote.port=" + javaCommand.getJmxPort(),
"-Dcom.sun.management.jmxremote.authenticate=false",
"-Dcom.sun.management.jmxremote.ssl=false",
"-Djava.rmi.server.hostname=" + LoopbackAddress.get().getHostAddress());
}

private List<String> buildClasspath(JavaCommand javaCommand) {
return Arrays.asList("-cp", StringUtils.join(javaCommand.getClasspath(), System.getProperty("path.separator")));
}
@@ -105,14 +100,12 @@ public class JavaProcessLauncher {
private File buildPropertiesFile(JavaCommand javaCommand) {
File propertiesFile = null;
try {
propertiesFile = File.createTempFile("sq-conf", "properties");
propertiesFile = File.createTempFile("sq-process", "properties");
Properties props = new Properties();
props.putAll(javaCommand.getArguments());
props.setProperty(ProcessEntryPoint.PROPERTY_PROCESS_KEY, javaCommand.getKey());
props.setProperty(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, String.valueOf(javaCommand.isDebugMode()));
props.setProperty(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_TIMEOUT, String.valueOf(timeouts.getAutokillPingTimeout()));
props.setProperty(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_INTERVAL, String.valueOf(timeouts.getAutokillPingInterval()));
props.setProperty(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, String.valueOf(timeouts.getTerminationTimeout()));
props.setProperty(ProcessEntryPoint.PROPERTY_STATUS_PATH, javaCommand.getReadyFile().getAbsolutePath());
OutputStream out = new FileOutputStream(propertiesFile);
props.store(out, String.format("Temporary properties file for command [%s]", javaCommand.getKey()));
out.close();

+ 0
- 44
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JmxConnector.java Parādīt failu

@@ -1,44 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process.monitor;

/**
* Interactions with monitored process
*/
public interface JmxConnector {

/**
* Throws an exception if timeout reached
*/
void connect(JavaCommand command, ProcessRef processRef, long timeoutMs);

void ping(ProcessRef process);

/**
* Throws an exception if timeout reached
*/
boolean isReady(ProcessRef process, long timeoutMs);

/**
* Throws an exception if timeout reached
*/
void terminate(ProcessRef process, long timeoutMs);

}

+ 9
- 53
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java Parādīt failu

@@ -21,8 +21,7 @@ package org.sonar.process.monitor;

import org.slf4j.LoggerFactory;
import org.sonar.process.Lifecycle;
import org.sonar.process.MessageException;
import org.sonar.process.State;
import org.sonar.process.Lifecycle.State;
import org.sonar.process.SystemExit;

import java.util.List;
@@ -33,28 +32,23 @@ public class Monitor {
private final List<ProcessRef> processes = new CopyOnWriteArrayList<ProcessRef>();
private final TerminatorThread terminator;
private final JavaProcessLauncher launcher;
private final JmxConnector jmxConnector;
private final Lifecycle lifecycle = new Lifecycle();
private final Timeouts timeouts;

private final SystemExit systemExit;
private Thread shutdownHook = new Thread(new MonitorShutdownHook(), "Monitor Shutdown Hook");

// used by awaitTermination() to block until all processes are shutdown
// used by awaitStop() to block until all processes are shutdown
private final List<WatcherThread> watcherThreads = new CopyOnWriteArrayList<WatcherThread>();

Monitor(JavaProcessLauncher launcher, JmxConnector jmxConnector, Timeouts timeouts, SystemExit exit) {
Monitor(JavaProcessLauncher launcher, SystemExit exit) {
this.launcher = launcher;
this.jmxConnector = jmxConnector;
this.timeouts = timeouts;
this.terminator = new TerminatorThread(processes, jmxConnector, timeouts);
this.terminator = new TerminatorThread(processes);
this.systemExit = exit;
}

public static Monitor create() {
Timeouts timeouts = new Timeouts();
return new Monitor(new JavaProcessLauncher(timeouts), new RmiJmxConnector(),
timeouts, new SystemExit());
return new Monitor(new JavaProcessLauncher(timeouts), new SystemExit());
}

/**
@@ -103,44 +97,14 @@ public class Monitor {
watcherThread.start();
watcherThreads.add(watcherThread);

// add to list of monitored processes only when successfully connected to it
jmxConnector.connect(command, processRef, timeouts.getJmxConnectionTimeout());
processes.add(processRef);

// ping process on a regular basis
processRef.setPingEnabled(!command.isDebugMode());
if (processRef.isPingEnabled()) {
PingerThread.startPinging(processRef, jmxConnector, timeouts);
}

// wait for process to be ready (accept requests or so on)
waitForReady(processRef);
processRef.waitForReady();

LoggerFactory.getLogger(getClass()).info(String.format("%s is up", processRef));
}

private void waitForReady(ProcessRef processRef) {
boolean ready = false;
while (!ready) {
if (processRef.isTerminated()) {
throw new MessageException(String.format("%s failed to start", processRef));
}
try {
ready = jmxConnector.isReady(processRef, timeouts.getMonitorIsReadyTimeout());
} catch (Exception ignored) {
// failed to send request, probably because RMI server is still not alive
// trying again, as long as process is alive
// TODO could be improved to have a STARTING timeout (to be implemented in monitor or
// in child process ?)
}
try {
Thread.sleep(300L);
} catch (InterruptedException e) {
throw new IllegalStateException("Interrupted while waiting for " + processRef + " to be ready", e);
}
}
}

/**
* Blocks until all processes are terminated
*/
@@ -160,14 +124,13 @@ public class Monitor {
* Blocks until all processes are terminated.
*/
public void stop() {
terminateAsync();
stopAsync();
try {
terminator.join();
} catch (InterruptedException ignored) {
// ignore, stop blocking
// stop blocking and exiting
}
// safeguard if TerminatorThread is buggy
hardKillAll();
lifecycle.tryToMoveTo(State.STOPPED);
systemExit.exit(0);
}
@@ -176,7 +139,7 @@ public class Monitor {
* Asks for processes termination and returns without blocking until termination.
* @return true if termination was requested, false if it was already being terminated
*/
boolean terminateAsync() {
boolean stopAsync() {
boolean requested = false;
if (lifecycle.tryToMoveTo(State.STOPPING)) {
requested = true;
@@ -185,13 +148,6 @@ public class Monitor {
return requested;
}

private void hardKillAll() {
// no specific order, kill'em all!!!
for (ProcessRef process : processes) {
process.hardKill();
}
}

public State getState() {
return lifecycle.getState();
}

+ 0
- 60
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/PingerThread.java Parādīt failu

@@ -1,60 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process.monitor;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
* This thread pings a process - through RMI - at fixed delay
*/
class PingerThread extends Thread {

private final ProcessRef processRef;
private final JmxConnector jmxConnector;

private PingerThread(ProcessRef process, JmxConnector jmxConnector) {
// it's important to give a name for traceability in profiling tools like visualVM
super(String.format("Ping[%s]", process.getKey()));
setDaemon(true);
this.processRef = process;
this.jmxConnector = jmxConnector;
}

@Override
public void run() {
if (!processRef.isTerminated() && processRef.isPingEnabled()) {
try {
jmxConnector.ping(processRef);
} catch (Exception ignored) {
// failed to ping
}
} else {
interrupt();
}
}

static void startPinging(ProcessRef processRef, JmxConnector jmxConnector, Timeouts timeouts) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
PingerThread pinger = new PingerThread(processRef, jmxConnector);
scheduler.scheduleAtFixedRate(pinger, 0L, timeouts.getMonitorPingInterval(), TimeUnit.MILLISECONDS);
}
}

+ 46
- 33
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java Parādīt failu

@@ -19,21 +19,26 @@
*/
package org.sonar.process.monitor;

import org.slf4j.LoggerFactory;
import org.sonar.process.MessageException;
import org.sonar.process.ProcessUtils;
import org.sonar.process.SharedStatus;

class ProcessRef {

private final String key;
private final SharedStatus sharedStatus;
private final Process process;
private final StreamGobbler[] gobblers;
private volatile boolean terminated = false;
private volatile boolean pingEnabled = true;
private final StreamGobbler gobbler;
private long launchedAt;
private volatile boolean stopped = false;

ProcessRef(String key, Process process, StreamGobbler... gobblers) {
ProcessRef(String key, SharedStatus sharedStatus, Process process, StreamGobbler gobbler) {
this.key = key;
this.sharedStatus = sharedStatus;
this.process = process;
this.terminated = !ProcessUtils.isAlive(process);
this.gobblers = gobblers;
this.stopped = !ProcessUtils.isAlive(process);
this.gobbler = gobbler;
}

/**
@@ -50,45 +55,53 @@ class ProcessRef {
return process;
}

/**
* Almost real-time status
*/
boolean isTerminated() {
return terminated;
void waitForReady() {
boolean ready = false;
while (!ready) {
if (isStopped()) {
throw new MessageException(String.format("%s failed to start", this));
}
ready = sharedStatus.wasStartedAfter(launchedAt);
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
throw new IllegalStateException(String.format("Interrupted while waiting for %s to be ready", this), e);
}
}
}

/**
* Sending pings can be disabled when requesting for termination or when process is on debug mode (JDWP)
*/
void setPingEnabled(boolean b) {
this.pingEnabled = b;
void setLaunchedAt(long launchedAt) {
this.launchedAt = launchedAt;
}

boolean isPingEnabled() {
return pingEnabled;
/**
* Almost real-time status
*/
boolean isStopped() {
return stopped;
}

/**
* Destroy the process without gracefully asking it to terminate (kill -9).
* @return true if the process was killed, false if process is already terminated
* Sends kill signal and awaits termination.
*/
boolean hardKill() {
boolean killed = false;
terminated = true;
pingEnabled = false;
void kill() {
if (ProcessUtils.isAlive(process)) {
ProcessUtils.destroyQuietly(process);
killed = true;
}
for (StreamGobbler gobbler : gobblers) {
StreamGobbler.waitUntilFinish(gobbler);
LoggerFactory.getLogger(getClass()).info(String.format("%s is stopping", this));
ProcessUtils.sendKillSignal(process);
try {
// signal is sent, waiting for shutdown hooks to be executed
process.waitFor();
StreamGobbler.waitUntilFinish(gobbler);
ProcessUtils.closeStreams(process);
} catch (InterruptedException ignored) {
// can't wait for the termination of process. Let's assume it's down.
}
}
ProcessUtils.closeStreams(process);
return killed;
stopped = true;
}

void setTerminated(boolean b) {
this.terminated = b;
void setStopped(boolean b) {
this.stopped = b;
}

@Override

+ 0
- 145
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/RmiJmxConnector.java Parādīt failu

@@ -1,145 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process.monitor;

import org.slf4j.LoggerFactory;
import org.sonar.process.JmxUtils;
import org.sonar.process.LoopbackAddress;
import org.sonar.process.ProcessMXBean;
import org.sonar.process.ProcessUtils;

import javax.annotation.CheckForNull;
import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

class RmiJmxConnector implements JmxConnector {

static {
/*
* Prevents such warnings :
*
* WARNING: Failed to restart: java.io.IOException: Failed to get a RMI stub: javax.naming.ServiceUnavailableException [Root exception
* is java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is:
* java.net.ConnectException: Connection refused]
* Sep 11, 2014 7:32:32 PM RMIConnector RMIClientCommunicatorAdmin-doStop
* WARNING: Failed to call the method close():java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is:
* java.net.ConnectException: Connection refused
* Sep 11, 2014 7:32:32 PM ClientCommunicatorAdmin Checker-run
* WARNING: Failed to check connection: java.net.ConnectException: Connection refused
* Sep 11, 2014 7:32:32 PM ClientCommunicatorAdmin Checker-run
* WARNING: stopping
*/
System.setProperty("sun.rmi.transport.tcp.logLevel", "SEVERE");
}

private final Map<ProcessRef, ProcessMXBean> mbeans = new IdentityHashMap<ProcessRef, ProcessMXBean>();

@Override
public synchronized void connect(final JavaCommand command, ProcessRef processRef, long timeoutMs) {
ConnectorCallable callable = new ConnectorCallable(command, processRef.getProcess());
ProcessMXBean mxBean = execute(callable, timeoutMs);
if (mxBean != null) {
register(processRef, mxBean);
}
}

@Override
public void ping(ProcessRef processRef) {
mbeans.get(processRef).ping();
}

@Override
public boolean isReady(final ProcessRef processRef, long timeoutMs) {
return execute(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return mbeans.get(processRef).isReady();
}
}, timeoutMs);
}

@Override
public void terminate(final ProcessRef processRef, long timeoutMs) {
execute(new Callable() {
@Override
public Void call() throws Exception {
LoggerFactory.getLogger(getClass()).info("Request termination of " + processRef);
mbeans.get(processRef).terminate();
return null;
}
}, timeoutMs);
}

void register(ProcessRef processRef, ProcessMXBean mxBean) {
mbeans.put(processRef, mxBean);
}

private <T> T execute(Callable<T> callable, long timeoutMs) {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<T> future = executor.submit(callable);
return future.get(timeoutMs, TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw new IllegalStateException("Fail send JMX request", e);
} finally {
executor.shutdownNow();
}
}

private static class ConnectorCallable implements Callable<ProcessMXBean> {
private final JavaCommand command;
private final Process process;

private ConnectorCallable(JavaCommand command, Process process) {
this.command = command;
this.process = process;
}

@Override
@CheckForNull
public ProcessMXBean call() throws Exception {
JMXServiceURL jmxUrl = JmxUtils.serviceUrl(LoopbackAddress.get(), command.getJmxPort());
while (ProcessUtils.isAlive(process)) {
try {
JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxUrl, null);
MBeanServerConnection mBeanServer = jmxConnector.getMBeanServerConnection();
return JMX.newMBeanProxy(mBeanServer, JmxUtils.objectName(command.getKey()), ProcessMXBean.class);
} catch (Exception ignored) {
// ignored, RMI server is probably not started yet
}
Thread.sleep(300L);
}

// process went down, no need to connect
return null;
}
}
}

+ 2
- 2
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/StreamGobbler.java Parādīt failu

@@ -55,8 +55,8 @@ class StreamGobbler extends Thread {
while ((line = br.readLine()) != null) {
logger.info(line);
}
} catch (Exception e) {
logger.error("Fail to read process logs", e);
} catch (Exception ignored) {
// ignored
} finally {
IOUtils.closeQuietly(br);
}

+ 3
- 20
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/TerminatorThread.java Parādīt failu

@@ -19,8 +19,6 @@
*/
package org.sonar.process.monitor;

import org.slf4j.LoggerFactory;

import java.util.List;

/**
@@ -31,33 +29,18 @@ import java.util.List;
class TerminatorThread extends Thread {

private final List<ProcessRef> processes;
private final JmxConnector jmxConnector;
private final Timeouts timeouts;

TerminatorThread(List<ProcessRef> processes, JmxConnector jmxConnector, Timeouts timeouts) {
TerminatorThread(List<ProcessRef> processes) {
super("Terminator");
this.processes = processes;
this.jmxConnector = jmxConnector;
this.timeouts = timeouts;
}

@Override
public void run() {
// terminate in reverse order of startup (dependency order)
for (int index = processes.size() - 1; index >= 0; index--) {
final ProcessRef processRef = processes.get(index);
if (!processRef.isTerminated()) {
processRef.setPingEnabled(false);
try {
jmxConnector.terminate(processRef, timeouts.getTerminationTimeout());
} catch (Exception ignored) {
// failed to gracefully stop in a timely fashion
LoggerFactory.getLogger(getClass()).info(String.format("Kill %s", processRef));
} finally {
// kill even if graceful termination was done, just to be sure that physical process is really down
processRef.hardKill();
}
}
ProcessRef processRef = processes.get(index);
processRef.kill();
}
}
}

+ 0
- 75
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Timeouts.java Parādīt failu

@@ -25,81 +25,6 @@ package org.sonar.process.monitor;
class Timeouts {

private long terminationTimeout = 120000L;
private long jmxConnectionTimeout = 15000L;
private long monitorPingInterval = 3000L;
private long monitorIsReadyTimeout = 10000L;
private long autokillPingTimeout = 60000L;
private long autokillPingInterval = 3000L;

/**
* [monitor] Timeout to get connected to RMI MXBean while process is alive
*/
long getJmxConnectionTimeout() {
return jmxConnectionTimeout;
}

/**
* @see #getJmxConnectionTimeout()
*/
void setJmxConnectionTimeout(long l) {
this.jmxConnectionTimeout = l;
}

/**
* [monitor] Delay between each ping request
*/
long getMonitorPingInterval() {
return monitorPingInterval;
}

/**
* @see #getMonitorPingInterval()
*/
void setMonitorPingInterval(long l) {
this.monitorPingInterval = l;
}

/**
* [monitor] Timeout of isReady request
*/
long getMonitorIsReadyTimeout() {
return monitorIsReadyTimeout;
}

/**
* @see #getMonitorIsReadyTimeout()
*/
void setMonitorIsReadyTimeout(long l) {
this.monitorIsReadyTimeout = l;
}

/**
* [monitored process] maximum age of last received ping before process autokills
*/
long getAutokillPingTimeout() {
return autokillPingTimeout;
}

/**
* @see #getAutokillPingTimeout()
*/
void setAutokillPingTimeout(long l) {
this.autokillPingTimeout = l;
}

/**
* [monitored process] delay between checks of freshness of received pings
*/
long getAutokillPingInterval() {
return autokillPingInterval;
}

/**
* @see #getAutokillPingInterval()
*/
void setAutokillPingInterval(long l) {
this.autokillPingInterval = l;
}

/**
* [both monitor and monitored process] timeout of graceful termination before hard killing

+ 6
- 6
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java Parādīt failu

@@ -46,16 +46,16 @@ class WatcherThread extends Thread {

@Override
public void run() {
boolean alive = true;
while (alive) {
boolean stopped = false;
while (!stopped) {
try {
processRef.getProcess().waitFor();
processRef.setTerminated(true);
alive = false;
LoggerFactory.getLogger(getClass()).info(String.format("%s is down", processRef));
processRef.setStopped(true);
stopped = true;
LoggerFactory.getLogger(getClass()).info(String.format("%s is stopped", processRef));

// terminate all other processes, but in another thread
monitor.stop();
monitor.stopAsync();
} catch (InterruptedException ignored) {
// continue to watch process
}

+ 0
- 34
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/CallVerifierJmxConnector.java Parādīt failu

@@ -1,34 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process.monitor;

/**
* Used to verify that pings were sent or not.
*/
public class CallVerifierJmxConnector extends RmiJmxConnector {

boolean askedPing = false;

@Override
public void ping(ProcessRef process) {
askedPing = true;
super.ping(process);
}
}

+ 0
- 42
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/ImpossibleToConnectJmxConnector.java Parādīt failu

@@ -1,42 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process.monitor;

public class ImpossibleToConnectJmxConnector implements JmxConnector {
@Override
public void connect(JavaCommand command, ProcessRef processRef, long timeoutMs) {
throw new IllegalStateException("Test - Impossible to connect to JMX");
}

@Override
public void ping(ProcessRef process) {

}

@Override
public boolean isReady(ProcessRef process, long timeoutMs) {
return false;
}

@Override
public void terminate(ProcessRef process, long timeoutMs) {

}
}

+ 2
- 15
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java Parādīt failu

@@ -42,7 +42,6 @@ public class JavaCommandTest {
args.setProperty("second_arg", "val2");
command.setArguments(args);

command.setJmxPort(1234);
command.setClassName("org.sonar.ElasticSearch");
command.setEnvVariable("JAVA_COMMAND_TEST", "1000");
File tempDir = temp.newFolder();
@@ -55,9 +54,9 @@ public class JavaCommandTest {

assertThat(command.toString()).isNotNull();
assertThat(command.getClasspath()).containsOnly("lib/*.jar", "conf/*.xml");
assertThat(command.getJavaOptions()).containsOnly("-Xmx128m", "-Djava.io.tmpdir=" + tempDir.getAbsolutePath());
assertThat(command.getJavaOptions()).containsOnly("-Xmx128m");
assertThat(command.getWorkDir()).isSameAs(workDir);
assertThat(command.getJmxPort()).isEqualTo(1234);
assertThat(command.getTempDir()).isSameAs(tempDir);
assertThat(command.getClassName()).isEqualTo("org.sonar.ElasticSearch");

// copy current env variables
@@ -65,18 +64,6 @@ public class JavaCommandTest {
assertThat(command.getEnvVariables().size()).isEqualTo(System.getenv().size() + 1);
}

@Test
public void test_debug_mode() throws Exception {
JavaCommand command = new JavaCommand("es");
assertThat(command.isDebugMode()).isFalse();

command.addJavaOption("-Xmx512m");
assertThat(command.isDebugMode()).isFalse();

command.addJavaOption("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005");
assertThat(command.isDebugMode()).isTrue();
}

@Test
public void split_java_options() throws Exception {
JavaCommand command = new JavaCommand("foo");

+ 1
- 2
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java Parādīt failu

@@ -20,7 +20,6 @@
package org.sonar.process.monitor;

import org.junit.Test;
import org.sonar.process.NetworkUtils;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
@@ -29,7 +28,7 @@ public class JavaProcessLauncherTest {

@Test
public void fail_to_launch() throws Exception {
JavaCommand command = new JavaCommand("test").setJmxPort(NetworkUtils.freePort());
JavaCommand command = new JavaCommand("test");
JavaProcessLauncher launcher = new JavaProcessLauncher(new Timeouts());
try {
// command is not correct (missing options), java.lang.ProcessBuilder#start()

+ 10
- 96
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java Parādīt failu

@@ -28,7 +28,7 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.Timeout;
import org.sonar.process.NetworkUtils;
import org.sonar.process.State;
import org.sonar.process.Lifecycle.State;
import org.sonar.process.SystemExit;

import java.io.File;
@@ -159,24 +159,7 @@ public class MonitorTest {
}

@Test
public void fail_to_connect_to_jmx() throws Exception {
Timeouts timeouts = new Timeouts();
monitor = new Monitor(new JavaProcessLauncher(timeouts),
new ImpossibleToConnectJmxConnector(), timeouts, exit);

HttpProcessClient p1 = new HttpProcessClient("p1");
try {
monitor.start(Arrays.asList(p1.newCommand()));
fail();
} catch (Exception e) {
// process was correctly launched, but there was a problem with RMI
assertThat(p1.isReady()).isFalse();
assertThat(p1.wasGracefullyTerminated()).isFalse();
}
}

@Test
public void terminate_all_processes_if_monitor_shutdowns() throws Exception {
public void stop_all_processes_if_monitor_shutdowns() throws Exception {
monitor = newDefaultMonitor();
HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2");
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
@@ -192,7 +175,7 @@ public class MonitorTest {
}

@Test
public void terminate_all_processes_if_one_monitored_process_shutdowns() throws Exception {
public void stop_all_processes_if_one_shutdowns() throws Exception {
monitor = newDefaultMonitor();
HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2");
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
@@ -210,9 +193,9 @@ public class MonitorTest {
}

@Test
public void terminate_all_processes_if_one_fails_to_start() throws Exception {
public void stop_all_processes_if_one_fails_to_start() throws Exception {
monitor = newDefaultMonitor();
HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2", -1, NetworkUtils.freePort());
HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2", -1);
try {
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
fail();
@@ -226,18 +209,8 @@ public class MonitorTest {
}

@Test
public void kill_process_if_fail_to_request_gracefully_termination() throws Exception {
Timeouts timeouts = new Timeouts();
timeouts.setTerminationTimeout(100L);
monitor = new Monitor(new JavaProcessLauncher(timeouts),
new TerminationFailureRmiConnector(), timeouts, exit);

HttpProcessClient p1 = new HttpProcessClient("p1");
monitor.start(Arrays.asList(p1.newCommand()));
assertThat(p1.isReady()).isTrue();

monitor.stop();
assertThat(p1.isReady()).isFalse();
public void force_stop_if_too_long() throws Exception {
// TODO
}

@Test
@@ -246,7 +219,6 @@ public class MonitorTest {
JavaCommand command = new JavaCommand("test")
.addClasspath(testJar.getAbsolutePath())
.setClassName("org.sonar.process.test.Unknown")
.setJmxPort(NetworkUtils.freePort())
.setTempDir(temp.newFolder());

try {
@@ -258,63 +230,9 @@ public class MonitorTest {
}
}

@Test
public void terminate_all_if_one_monitored_process_shutdowns() throws Exception {
monitor = newDefaultMonitor();
HttpProcessClient client = new HttpProcessClient("test");
// blocks until started
monitor.start(Arrays.asList(client.newCommand()));
assertThat(client.isReady()).isTrue();

client.kill();
assertThat(client.isReady()).isFalse();

// does not wait, already terminated
monitor.awaitTermination();

// TODO check logs
}

@Test
public void fail_if_jmx_port_is_not_available() throws Exception {
monitor = newDefaultMonitor();
// c1 and c2 have same JMX port
int jmxPort = NetworkUtils.freePort();
HttpProcessClient p1 = new HttpProcessClient("p1", NetworkUtils.freePort(), jmxPort);
HttpProcessClient p2 = new HttpProcessClient("p2", NetworkUtils.freePort(), jmxPort);
try {
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
fail();
} catch (Exception expected) {
assertThat(p1.wasReady()).isTrue();
assertThat(p2.wasReady()).isFalse();
assertThat(p1.isReady()).isFalse();
assertThat(p2.isReady()).isFalse();
}
}

@Test
public void disable_autokill_on_jvm_debug_mode() throws Exception {
Timeouts timeouts = new Timeouts();
timeouts.setMonitorPingInterval(10L);
timeouts.setAutokillPingInterval(10L);
timeouts.setAutokillPingTimeout(10L);
CallVerifierJmxConnector jmxConnector = new CallVerifierJmxConnector();
monitor = new Monitor(new JavaProcessLauncher(timeouts), jmxConnector, timeouts, exit);

JavaCommand command = newStandardProcessCommand()
.addJavaOption("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=" + NetworkUtils.freePort());
monitor.start(Arrays.asList(command));

Thread.sleep(20L);
assertThat(jmxConnector.askedPing).isFalse();

monitor.stop();
}

private Monitor newDefaultMonitor() {
Timeouts timeouts = new Timeouts();
return new Monitor(new JavaProcessLauncher(timeouts), new RmiJmxConnector(), timeouts, exit);
return new Monitor(new JavaProcessLauncher(timeouts), exit);
}

/**
@@ -324,27 +242,24 @@ public class MonitorTest {
private final int httpPort;
private final String commandKey;
private final File tempDir;
private int jmxPort;

private HttpProcessClient(String commandKey) throws IOException {
this(commandKey, NetworkUtils.freePort(), NetworkUtils.freePort());
this(commandKey, NetworkUtils.freePort());
}

/**
* Use httpPort=-1 to make server fail to start
*/
private HttpProcessClient(String commandKey, int httpPort, int jmxPort) throws IOException {
private HttpProcessClient(String commandKey, int httpPort) throws IOException {
this.commandKey = commandKey;
this.tempDir = temp.newFolder(commandKey);
this.httpPort = httpPort;
this.jmxPort = jmxPort;
}

JavaCommand newCommand() throws IOException {
return new JavaCommand(commandKey)
.addClasspath(testJar.getAbsolutePath())
.setClassName("org.sonar.process.test.HttpProcess")
.setJmxPort(jmxPort)
.setArgument("httpPort", String.valueOf(httpPort))
.setTempDir(tempDir);
}
@@ -416,7 +331,6 @@ public class MonitorTest {
return new JavaCommand("standard")
.addClasspath(testJar.getAbsolutePath())
.setClassName("org.sonar.process.test.StandardProcess")
.setJmxPort(NetworkUtils.freePort())
.setTempDir(temp.newFolder());
}


+ 0
- 71
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/RmiJmxConnectorTest.java Parādīt failu

@@ -1,71 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process.monitor;

import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.sonar.process.NetworkUtils;
import org.sonar.process.ProcessMXBean;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class RmiJmxConnectorTest {

@Test
public void fail_to_connect_in_timely_fashion() throws Exception {
RmiJmxConnector connector = new RmiJmxConnector();
ProcessRef ref = mock(ProcessRef.class);

JavaCommand command = new JavaCommand("foo").setJmxPort(NetworkUtils.freePort());
try {
connector.connect(command, ref, 0L);
fail();
} catch (IllegalStateException e) {
// ok
}
}

@Test
public void throw_exception_on_timeout() throws Exception {
RmiJmxConnector connector = new RmiJmxConnector();
ProcessRef ref = mock(ProcessRef.class);
ProcessMXBean mxBean = mock(ProcessMXBean.class);
connector.register(ref, mxBean);

when(mxBean.isReady()).thenAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable {
Thread.sleep(Long.MAX_VALUE);
return null;
}
});

try {
connector.isReady(ref, 5L);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Fail send JMX request");
}
}
}

+ 0
- 27
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TerminationFailureRmiConnector.java Parādīt failu

@@ -1,27 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process.monitor;

public class TerminationFailureRmiConnector extends RmiJmxConnector {
@Override
public void terminate(ProcessRef processRef, long timeoutMs) {
throw new IllegalStateException("Test - fail to send termination request");
}
}

+ 0
- 16
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TimeoutsTest.java Parādīt failu

@@ -28,29 +28,13 @@ public class TimeoutsTest {
@Test
public void test_default_values() throws Exception {
Timeouts timeouts = new Timeouts();
assertThat(timeouts.getMonitorPingInterval()).isGreaterThan(1000L);
assertThat(timeouts.getAutokillPingInterval()).isGreaterThan(1000L);
assertThat(timeouts.getAutokillPingTimeout()).isGreaterThan(1000L);
assertThat(timeouts.getTerminationTimeout()).isGreaterThan(1000L);
assertThat(timeouts.getJmxConnectionTimeout()).isGreaterThan(1000L);
assertThat(timeouts.getMonitorIsReadyTimeout()).isGreaterThan(1000L);
}

@Test
public void test_values() throws Exception {
Timeouts timeouts = new Timeouts();
timeouts.setAutokillPingInterval(1L);
timeouts.setAutokillPingTimeout(2L);
timeouts.setTerminationTimeout(3L);
timeouts.setJmxConnectionTimeout(4L);
timeouts.setMonitorPingInterval(5L);
timeouts.setMonitorIsReadyTimeout(6L);

assertThat(timeouts.getAutokillPingInterval()).isEqualTo(1L);
assertThat(timeouts.getAutokillPingTimeout()).isEqualTo(2L);
assertThat(timeouts.getTerminationTimeout()).isEqualTo(3L);
assertThat(timeouts.getJmxConnectionTimeout()).isEqualTo(4L);
assertThat(timeouts.getMonitorPingInterval()).isEqualTo(5L);
assertThat(timeouts.getMonitorIsReadyTimeout()).isEqualTo(6L);
}
}

+ 1
- 1
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/WatcherThreadTest.java Parādīt failu

@@ -34,6 +34,6 @@ public class WatcherThreadTest {
WatcherThread watcher = new WatcherThread(ref, monitor);
watcher.start();
watcher.join();
verify(monitor).stop();
verify(monitor).stopAsync();
}
}

+ 0
- 81
server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java Parādīt failu

@@ -1,81 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXServiceURL;

import java.lang.management.ManagementFactory;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.MalformedURLException;

public class JmxUtils {

private JmxUtils() {
// only static stuff
}

public static final String DOMAIN = "org.sonar";
public static final String NAME_PROPERTY = "name";

public static final String WEB_SERVER_NAME = "web";
public static final String SEARCH_SERVER_NAME = "search";

public static ObjectName objectName(String name) {
try {
return new ObjectName(DOMAIN, NAME_PROPERTY, name);
} catch (MalformedObjectNameException e) {
throw new IllegalStateException("Cannot create ObjectName for " + name, e);
}
}

public static void registerMBean(Object mbean, String name) {
try {
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName oName = objectName(name);
// Check if already registered in JVM (might run multiple instance in JUnits)
if (mbeanServer.isRegistered(oName)) {
mbeanServer.unregisterMBean(oName);
}
mbeanServer.registerMBean(mbean, oName);
} catch (RuntimeException re) {
throw re;
} catch (Exception e) {
throw new IllegalStateException("Fail to register JMX MBean named " + name, e);
}
}

public static JMXServiceURL serviceUrl(InetAddress host, int port) {
String address = host.getHostAddress();
if (host instanceof Inet6Address) {
// See http://docs.oracle.com/javase/7/docs/api/javax/management/remote/JMXServiceURL.html
// "The host is a host name, an IPv4 numeric host address, or an IPv6 numeric address enclosed in square brackets."
address = String.format("[%s]", address);
}
try {
return new JMXServiceURL("rmi", address, port, String.format("/jndi/rmi://%s:%d/jmxrmi", address, port));
} catch (MalformedURLException e) {
throw new IllegalStateException("JMX url does not look well formed", e);
}
}
}

+ 4
- 2
server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java Parādīt failu

@@ -21,6 +21,10 @@ package org.sonar.process;

public class Lifecycle {

public static enum State {
INIT, STARTING, STARTED, STOPPING, STOPPED
}

private State state = State.INIT;

public State getState() {
@@ -35,7 +39,6 @@ public class Lifecycle {
return false;
}


@Override
public boolean equals(Object o) {
if (this == o) {
@@ -44,7 +47,6 @@ public class Lifecycle {
if (o == null || getClass() != o.getClass()) {
return false;
}

Lifecycle lifecycle = (Lifecycle) o;
return state == lifecycle.state;
}

+ 3
- 0
server/sonar-process/src/main/java/org/sonar/process/MessageException.java Parādīt failu

@@ -19,6 +19,9 @@
*/
package org.sonar.process;

/**
* Unchecked exception that is displayed without stacktrace
*/
public class MessageException extends RuntimeException {
public MessageException(String message) {
super(message);

server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java → server/sonar-process/src/main/java/org/sonar/process/Monitored.java Parādīt failu

@@ -19,7 +19,7 @@
*/
package org.sonar.process;

public interface MonitoredProcess extends Terminable {
public interface Monitored {

/**
* Starts process. No need to block until fully started and operational.
@@ -36,6 +36,7 @@ public interface MonitoredProcess extends Terminable {
/**
* Blocks until the process is terminated
*/
void awaitTermination();
void awaitStop();

void stop();
}

+ 10
- 4
server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java Parādīt failu

@@ -19,6 +19,8 @@
*/
package org.sonar.process;

import org.apache.commons.io.IOUtils;

import java.net.ServerSocket;

public class NetworkUtils {
@@ -27,14 +29,18 @@ public class NetworkUtils {
// only static stuff
}

/**
* Get an unused port
*/
public static int freePort() {
ServerSocket s = null;
try {
ServerSocket s = new ServerSocket(0);
int port = s.getLocalPort();
s.close();
return port;
s = new ServerSocket(0);
return s.getLocalPort();
} catch (Exception e) {
throw new IllegalStateException("Can not find an open network port", e);
} finally {
IOUtils.closeQuietly(s);
}
}
}

+ 26
- 60
server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java Parādīt failu

@@ -21,24 +21,19 @@ package org.sonar.process;

import org.slf4j.LoggerFactory;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ProcessEntryPoint implements ProcessMXBean {
public class ProcessEntryPoint {

public static final String PROPERTY_PROCESS_KEY = "process.key";
public static final String PROPERTY_AUTOKILL_DISABLED = "process.autokill.disabled";
public static final String PROPERTY_AUTOKILL_PING_TIMEOUT = "process.autokill.pingTimeout";
public static final String PROPERTY_AUTOKILL_PING_INTERVAL = "process.autokill.pingInterval";
public static final String PROPERTY_TERMINATION_TIMEOUT = "process.terminationTimeout";
public static final String PROPERTY_STATUS_PATH = "process.statusPath";

private final Props props;
private final Lifecycle lifecycle = new Lifecycle();
private volatile MonitoredProcess monitoredProcess;
private volatile long lastPing = 0L;
private final SharedStatus sharedStatus;
private volatile Monitored monitored;
private volatile StopperThread stopperThread;
private final SystemExit exit;

private Thread shutdownHook = new Thread(new Runnable() {
@Override
public void run() {
@@ -47,9 +42,10 @@ public class ProcessEntryPoint implements ProcessMXBean {
}
});

ProcessEntryPoint(Props props, SystemExit exit) {
ProcessEntryPoint(Props props, SystemExit exit, SharedStatus sharedStatus) {
this.props = props;
this.exit = exit;
this.sharedStatus = sharedStatus;
}

public Props getProps() {
@@ -59,29 +55,25 @@ public class ProcessEntryPoint implements ProcessMXBean {
/**
* Launch process and waits until it's down
*/
public void launch(MonitoredProcess mp) {
if (!lifecycle.tryToMoveTo(State.STARTING)) {
public void launch(Monitored mp) {
if (!lifecycle.tryToMoveTo(Lifecycle.State.STARTING)) {
throw new IllegalStateException("Already started");
}
monitoredProcess = mp;

// TODO check if these properties are available in System Info
JmxUtils.registerMBean(this, props.nonNullValue(PROPERTY_PROCESS_KEY));
Runtime.getRuntime().addShutdownHook(shutdownHook);
if (!props.valueAsBoolean(PROPERTY_AUTOKILL_DISABLED, false)) {
// mainly for Java Debugger
scheduleAutokill();
}
monitored = mp;

try {
monitoredProcess.start();
Runtime.getRuntime().addShutdownHook(shutdownHook);
monitored.start();
boolean ready = false;
while (!ready) {
ready = monitoredProcess.isReady();
ready = monitored.isReady();
Thread.sleep(200L);
}
if (lifecycle.tryToMoveTo(State.STARTED)) {
monitoredProcess.awaitTermination();

sharedStatus.setReady();

if (lifecycle.tryToMoveTo(Lifecycle.State.STARTED)) {
monitored.awaitStop();
}
} catch (Exception e) {
LoggerFactory.getLogger(getClass()).warn("Fail to start", e);
@@ -91,56 +83,30 @@ public class ProcessEntryPoint implements ProcessMXBean {
}
}

@Override
public boolean isReady() {
return lifecycle.getState() == State.STARTED;
}

@Override
public void ping() {
lastPing = System.currentTimeMillis();
boolean isStarted() {
return lifecycle.getState() == Lifecycle.State.STARTED;
}

/**
* Blocks until stopped in a timely fashion (see {@link org.sonar.process.StopperThread})
*/
@Override
public void terminate() {
if (lifecycle.tryToMoveTo(State.STOPPING)) {
stopperThread = new StopperThread(monitoredProcess, Long.parseLong(props.nonNullValue(PROPERTY_TERMINATION_TIMEOUT)));
void terminate() {
if (lifecycle.tryToMoveTo(Lifecycle.State.STOPPING)) {
stopperThread = new StopperThread(monitored, sharedStatus, Long.parseLong(props.nonNullValue(PROPERTY_TERMINATION_TIMEOUT)));
stopperThread.start();
}
try {
// stopperThread is not null for sure
// join() does nothing if thread already finished
stopperThread.join();
lifecycle.tryToMoveTo(State.STOPPED);
lifecycle.tryToMoveTo(Lifecycle.State.STOPPED);
} catch (InterruptedException e) {
// nothing to do, the process is going to be exited
}
exit.exit(0);
}

private void scheduleAutokill() {
final long autokillPingTimeoutMs = props.valueAsInt(PROPERTY_AUTOKILL_PING_TIMEOUT);
long autokillPingIntervalMs = props.valueAsInt(PROPERTY_AUTOKILL_PING_INTERVAL);
Runnable autokiller = new Runnable() {
@Override
public void run() {
long time = System.currentTimeMillis();
if (time - lastPing > autokillPingTimeoutMs) {
LoggerFactory.getLogger(getClass()).info(String.format(
"Did not receive any ping during %d seconds. Shutting down.", autokillPingTimeoutMs / 1000));
terminate();
}
}
};
lastPing = System.currentTimeMillis();
ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1);
monitor.scheduleWithFixedDelay(autokiller, autokillPingIntervalMs, autokillPingIntervalMs, TimeUnit.MILLISECONDS);
}

State getState() {
Lifecycle.State getState() {
return lifecycle.getState();
}

@@ -150,6 +116,6 @@ public class ProcessEntryPoint implements ProcessMXBean {

public static ProcessEntryPoint createForArguments(String[] args) {
Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args);
return new ProcessEntryPoint(props, new SystemExit());
return new ProcessEntryPoint(props, new SystemExit(), new SharedStatus(props.nonNullValueAsFile(PROPERTY_STATUS_PATH)));
}
}

+ 0
- 1
server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java Parādīt failu

@@ -41,7 +41,6 @@ public class ProcessLogging {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(context);

}

/**

+ 0
- 28
server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java Parādīt failu

@@ -1,28 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process;

public interface ProcessMXBean extends Terminable {

boolean isReady();

void ping();

}

+ 9
- 12
server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java Parādīt failu

@@ -47,28 +47,25 @@ public class ProcessUtils {
}

/**
* Destroys process (equivalent to kill -9) if alive
* @return true if the process was destroyed, false if process is null or already destroyed.
* Send kill signal to stop process. Shutdown hooks are executed. It's the equivalent of SIGTERM on Linux.
* Correctly tested on Java 6 and 7 on both Mac/MSWindows
* @return true if the signal is sent, false if process is already down
*/
public static boolean destroyQuietly(@Nullable Process process) {
boolean destroyed = false;
public static boolean sendKillSignal(@Nullable Process process) {
boolean sentSignal = false;
if (isAlive(process)) {
try {
process.destroy();
while (isAlive(process)) {
// destroy() sends the signal, it does not wait for the process to be down
Thread.sleep(100L);
}
destroyed = true;
sentSignal = true;
} catch (Exception e) {
LoggerFactory.getLogger(ProcessUtils.class).error("Fail to destroy " + process);
LoggerFactory.getLogger(ProcessUtils.class).error("Fail to kill " + process);
}
}
return destroyed;
return sentSignal;
}

public static void closeStreams(@Nullable Process process) {
if (process != null) {
if (process!=null) {
IOUtils.closeQuietly(process.getInputStream());
IOUtils.closeQuietly(process.getOutputStream());
IOUtils.closeQuietly(process.getErrorStream());

+ 1
- 1
server/sonar-process/src/main/java/org/sonar/process/Props.java Parādīt failu

@@ -77,7 +77,7 @@ public class Props {
public File nonNullValueAsFile(String key) {
String s = value(key);
if (s == null) {
throw new IllegalArgumentException("Property " + key + " is missing");
throw new IllegalArgumentException("Property " + key + " is not set");
}
return new File(s);
}

server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java → server/sonar-process/src/main/java/org/sonar/process/SharedStatus.java Parādīt failu

@@ -20,40 +20,44 @@
package org.sonar.process;

import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;

public abstract class BaseProcessTest {
public class SharedStatus {

@Rule
public TemporaryFolder temp = new TemporaryFolder();
private final File file;

public static final String DUMMY_OK_APP = "org.sonar.application.DummyOkProcess";

int freePort;
File dummyAppJar;
Process proc;
public SharedStatus(File file) {
this.file = file;
}

@Before
public void setup() throws IOException {
ServerSocket socket = new ServerSocket(0);
freePort = socket.getLocalPort();
socket.close();
/**
* Executed by monitor - remove existing shared file before starting child process
*/
public void prepare() {
if (file.exists()) {
if (!file.delete()) {
throw new MessageException(String.format(
"Fail to delete file %s. Please check that no SonarQube process is alive", file));
}
}
}

dummyAppJar = FileUtils.toFile(getClass().getResource("/sonar-dummy-app.jar"));
public boolean wasStartedAfter(long launchedAt) {
// File#lastModified() can have second precision on some OS
return file.exists() && file.lastModified() / 1000 >= launchedAt / 1000;
}

@After
public void tearDown() {
if (proc != null) {
ProcessUtils.destroyQuietly(proc);
public void setReady() {
try {
FileUtils.touch(file);
} catch (IOException e) {
throw new IllegalStateException("Fail to create file " + file, e);
}
}

public void setStopped() {
FileUtils.deleteQuietly(file);
}
}

+ 0
- 26
server/sonar-process/src/main/java/org/sonar/process/State.java Parādīt failu

@@ -1,26 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process;

public enum State {

INIT, STARTING, STARTED, STOPPING, STOPPED

}

+ 10
- 8
server/sonar-process/src/main/java/org/sonar/process/StopperThread.java Parādīt failu

@@ -27,17 +27,19 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
* Gracefully stops process, but exits JVM if too long
* Gracefully stops process in a timely fashion
*/
class StopperThread extends Thread {

private final Terminable terminable;
private final Monitored monitored;
private final long terminationTimeout;
private final SharedStatus sharedStatus;

StopperThread(Terminable terminable, long terminationTimeout) {
StopperThread(Monitored monitored, SharedStatus sharedStatus, long terminationTimeout) {
super("Stopper");
this.terminable = terminable;
this.monitored = monitored;
this.terminationTimeout = terminationTimeout;
this.sharedStatus = sharedStatus;
}

@Override
@@ -46,15 +48,15 @@ class StopperThread extends Thread {
Future future = executor.submit(new Runnable() {
@Override
public void run() {
terminable.terminate();
monitored.stop();
}
});
try {
future.get(terminationTimeout, TimeUnit.MILLISECONDS);
} catch (Exception e) {
LoggerFactory.getLogger(getClass()).error("Can not terminate in " + terminationTimeout + "ms", e);
} finally {
executor.shutdownNow();
LoggerFactory.getLogger(getClass()).error(String.format("Can not stop in %dms", terminationTimeout), e);
}
executor.shutdownNow();
sharedStatus.setStopped();
}
}

+ 0
- 28
server/sonar-process/src/main/java/org/sonar/process/Terminable.java Parādīt failu

@@ -1,28 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process;

/**
* This term "terminate" is used in order to not conflict with {@link Thread#stop()}.
*/
public interface Terminable {

void terminate();
}

+ 0
- 127
server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java Parādīt failu

@@ -1,127 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process;

import org.junit.Test;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXServiceURL;

import java.lang.management.ManagementFactory;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;

public class JmxUtilsTest {

class MyBean implements ProcessMXBean {


@Override
public void terminate() {

}

@Override
public void ping() {

}

@Override
public boolean isReady() {
return true;
}
}

@Test
public void construct_jmx_objectName() throws Exception {
MyBean mxBean = new MyBean();
ObjectName objectName = JmxUtils.objectName(mxBean.getClass().getSimpleName());
assertThat(objectName).isNotNull();
assertThat(objectName.getDomain()).isEqualTo(JmxUtils.DOMAIN);
assertThat(objectName.getKeyProperty(JmxUtils.NAME_PROPERTY)).isEqualTo(mxBean.getClass().getSimpleName());
}

@Test
public void fail_jmx_objectName() throws Exception {
try {
JmxUtils.objectName(":");
fail();
} catch (Exception e) {
assertThat(e.getMessage()).isEqualTo("Cannot create ObjectName for :");
}
}

@Test
public void register_mbean() throws Exception {
// 0 Get mbServer and create out test MXBean
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
MyBean mxBean = new MyBean();
ObjectName objectName = JmxUtils.objectName(mxBean.getClass().getSimpleName());

// 1 assert that mxBean gets registered
assertThat(mbeanServer.isRegistered(objectName)).isFalse();
JmxUtils.registerMBean(mxBean, mxBean.getClass().getSimpleName());
assertThat(mbeanServer.isRegistered(objectName)).isTrue();

try {
JmxUtils.registerMBean(new Object(), "");
fail();
} catch (IllegalStateException e) {
// ok
}
}

@Test
public void serviceUrl_ipv4() throws Exception {
JMXServiceURL url = JmxUtils.serviceUrl(ip(Inet4Address.class), 1234);
assertThat(url).isNotNull();
assertThat(url.getPort()).isEqualTo(1234);
}

@Test
public void serviceUrl_ipv6() throws Exception {
JMXServiceURL url = JmxUtils.serviceUrl(ip(Inet6Address.class), 1234);
assertThat(url).isNotNull();
assertThat(url.getPort()).isEqualTo(1234);
}

private static InetAddress ip(Class inetAddressClass) throws SocketException {
Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
while (ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
Enumeration<InetAddress> addresses = iface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
if (addr.getClass().isAssignableFrom(inetAddressClass)) {
return addr;
}
}
}
throw new IllegalStateException("no ipv4 address");
}
}

+ 13
- 0
server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java Parādīt failu

@@ -22,6 +22,7 @@ package org.sonar.process;
import org.junit.Test;

import static org.fest.assertions.Assertions.assertThat;
import static org.sonar.process.Lifecycle.State;

public class LifecycleTest {

@@ -39,4 +40,16 @@ public class LifecycleTest {
stopping.tryToMoveTo(State.STOPPING);
assertThat(stopping).isNotEqualTo(init);
}

@Test
public void try_to_move() throws Exception {
Lifecycle lifecycle = new Lifecycle();
assertThat(lifecycle.getState()).isEqualTo(State.INIT);

assertThat(lifecycle.tryToMoveTo(State.STARTED)).isTrue();
assertThat(lifecycle.getState()).isEqualTo(State.STARTED);

assertThat(lifecycle.tryToMoveTo(State.STARTING)).isFalse();
assertThat(lifecycle.getState()).isEqualTo(State.STARTED);
}
}

+ 2
- 3
server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java Parādīt failu

@@ -27,11 +27,10 @@ import static org.fest.assertions.Assertions.assertThat;

public class NetworkUtilsTest {


@Test
public void find_free_port() throws Exception {
int port = NetworkUtils.freePort();
assertThat(port).isGreaterThan(1024);
assertThat(port).isGreaterThan(0);
}

@Test
@@ -58,4 +57,4 @@ public class NetworkUtilsTest {

assertThat(port1).isNotSameAs(port2);
}
}
}

+ 17
- 52
server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java Parādīt failu

@@ -32,6 +32,7 @@ import java.util.Properties;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.mockito.Mockito.mock;
import static org.sonar.process.Lifecycle.State;

public class ProcessEntryPointTest {

@@ -49,47 +50,29 @@ public class ProcessEntryPointTest {
@Test
public void load_properties_from_file() throws Exception {
File propsFile = temp.newFile();
FileUtils.write(propsFile, "sonar.foo=bar");
FileUtils.write(propsFile, "sonar.foo=bar\nprocess.key=web\nprocess.statusPath=status.temp");

ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(new String[]{propsFile.getAbsolutePath()});
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(new String[] {propsFile.getAbsolutePath()});
assertThat(entryPoint.getProps().value("sonar.foo")).isEqualTo("bar");
assertThat(entryPoint.getProps().value("process.key")).isEqualTo("web");
}

@Test
public void test_initial_state() throws Exception {
Props props = new Props(new Properties());
ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, mock(SharedStatus.class));

assertThat(entryPoint.getProps()).isSameAs(props);
assertThat(entryPoint.isReady()).isFalse();
assertThat(entryPoint.isStarted()).isFalse();
assertThat(entryPoint.getState()).isEqualTo(State.INIT);

// do not fail
entryPoint.ping();
}

@Test
public void fail_to_launch_if_missing_monitor_properties() throws Exception {
Props props = new Props(new Properties());
ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);

StandardProcess process = new StandardProcess();
try {
entryPoint.launch(process);
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Missing property: process.key");
assertThat(process.getState()).isEqualTo(State.INIT);
}
}

@Test
public void fail_to_launch_multiple_times() throws Exception {
Props props = new Props(new Properties());
props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test");
props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true");
props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000");
ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, mock(SharedStatus.class));

entryPoint.launch(new NoopProcess());
try {
@@ -104,9 +87,8 @@ public class ProcessEntryPointTest {
public void launch_then_request_graceful_termination() throws Exception {
Props props = new Props(new Properties());
props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test");
props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true");
props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000");
final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, mock(SharedStatus.class));
final StandardProcess process = new StandardProcess();

Thread runner = new Thread() {
@@ -129,28 +111,12 @@ public class ProcessEntryPointTest {
assertThat(process.getState()).isEqualTo(State.STOPPED);
}

@Test
public void autokill_if_no_pings() throws Exception {
Props props = new Props(new Properties());
props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test");
props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000");
props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_INTERVAL, "5");
props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_TIMEOUT, "1");
final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
final StandardProcess process = new StandardProcess();

entryPoint.launch(process);

assertThat(process.getState()).isEqualTo(State.STOPPED);
}

@Test
public void terminate_if_unexpected_shutdown() throws Exception {
Props props = new Props(new Properties());
props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "foo");
props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true");
props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000");
final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, mock(SharedStatus.class));
final StandardProcess process = new StandardProcess();

Thread runner = new Thread() {
@@ -177,16 +143,15 @@ public class ProcessEntryPointTest {
public void terminate_if_startup_error() throws Exception {
Props props = new Props(new Properties());
props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "foo");
props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true");
props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000");
final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
final MonitoredProcess process = new StartupErrorProcess();
final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, mock(SharedStatus.class));
final Monitored process = new StartupErrorProcess();

entryPoint.launch(process);
assertThat(entryPoint.getState()).isEqualTo(State.STOPPED);
}

private static class NoopProcess implements MonitoredProcess {
private static class NoopProcess implements Monitored {

@Override
public void start() {
@@ -199,17 +164,17 @@ public class ProcessEntryPointTest {
}

@Override
public void awaitTermination() {
public void awaitStop() {

}

@Override
public void terminate() {
public void stop() {

}
}

private static class StartupErrorProcess implements MonitoredProcess {
private static class StartupErrorProcess implements Monitored {

@Override
public void start() {
@@ -222,12 +187,12 @@ public class ProcessEntryPointTest {
}

@Override
public void awaitTermination() {
public void awaitStop() {

}

@Override
public void terminate() {
public void stop() {

}
}

+ 0
- 28
server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java Parādīt failu

@@ -1,28 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process;

import org.junit.Test;

import static org.fest.assertions.Assertions.assertThat;

public class ProcessUtilsTest {

}

+ 101
- 0
server/sonar-process/src/test/java/org/sonar/process/SharedStatusTest.java Parādīt failu

@@ -0,0 +1,101 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.File;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class SharedStatusTest {

@Rule
public TemporaryFolder temp = new TemporaryFolder();

@Test
public void prepare() throws Exception {
File file = temp.newFile();
assertThat(file).exists();

SharedStatus sharedStatus = new SharedStatus(file);
sharedStatus.prepare();
assertThat(file).doesNotExist();
}

@Test
public void fail_to_prepare_if_file_is_locked() throws Exception {
File file = mock(File.class);
when(file.exists()).thenReturn(true);
when(file.delete()).thenReturn(false);

SharedStatus sharedStatus = new SharedStatus(file);
try {
sharedStatus.prepare();
fail();
} catch (MessageException e) {
// ok
}
}

@Test
public void create_file_when_ready_then_delete_when_stopped() throws Exception {
File file = new File(temp.newFolder(), "foo.txt");
assertThat(file).doesNotExist();

SharedStatus sharedStatus = new SharedStatus(file);
sharedStatus.setReady();
assertThat(file).exists();

sharedStatus.setStopped();
assertThat(file).doesNotExist();
}

@Test
public void was_started_after() throws Exception {
File file = mock(File.class);
SharedStatus sharedStatus = new SharedStatus(file);

// does not exist
when(file.exists()).thenReturn(false);
when(file.lastModified()).thenReturn(123456L);
assertThat(sharedStatus.wasStartedAfter(122000L)).isFalse();

// file created before
when(file.exists()).thenReturn(true);
when(file.lastModified()).thenReturn(123456L);
assertThat(sharedStatus.wasStartedAfter(124000L)).isFalse();

// file created after
when(file.exists()).thenReturn(true);
when(file.lastModified()).thenReturn(123456L);
assertThat(sharedStatus.wasStartedAfter(123123L)).isTrue();

// file created after, but can be truncated to second on some OS
when(file.exists()).thenReturn(true);
when(file.lastModified()).thenReturn(123000L);
assertThat(sharedStatus.wasStartedAfter(123456L)).isTrue();
}
}

+ 79
- 0
server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java Parādīt failu

@@ -0,0 +1,79 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.io.File;

import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

public class StopperThreadTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();


@Test(timeout = 3000L)
public void stop_in_a_timely_fashion() throws Exception {
File file = temp.newFile();
SharedStatus sharedStatus = new SharedStatus(file);
assertThat(file).exists();
Monitored monitored = mock(Monitored.class);

// max stop timeout is 5 seconds, but test fails after 3 seconds
// -> guarantees that stop is immediate
StopperThread stopper = new StopperThread(monitored, sharedStatus, 5000L);
stopper.start();
stopper.join();

verify(monitored).stop();
assertThat(file).doesNotExist();
}

@Test(timeout = 3000L)
public void stop_timeout() throws Exception {
File file = temp.newFile();
SharedStatus sharedStatus = new SharedStatus(file);
assertThat(file).exists();
Monitored monitored = mock(Monitored.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
Thread.sleep(10000L);
return null;
}
}).when(monitored).stop();

// max stop timeout is 10 milliseconds
StopperThread stopper = new StopperThread(monitored, sharedStatus, 10L);
stopper.start();
stopper.join();

verify(monitored).stop();
assertThat(file).doesNotExist();
}
}

+ 10
- 5
server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java Parādīt failu

@@ -24,7 +24,7 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.sonar.process.MonitoredProcess;
import org.sonar.process.Monitored;
import org.sonar.process.ProcessEntryPoint;

import javax.servlet.ServletException;
@@ -39,7 +39,7 @@ import java.io.IOException;
* It also pushes status to temp files, so test can verify what was really done (when server went ready state and
* if it was gracefully terminated)
*/
public class HttpProcess implements MonitoredProcess {
public class HttpProcess implements Monitored {

private final Server server;
private boolean ready = false;
@@ -78,18 +78,22 @@ public class HttpProcess implements MonitoredProcess {

@Override
public boolean isReady() {
System.out.println("received isReady()");
if (ready) {
return true;
}
System.out.println("checking server.isStarted()");
if (server.isStarted()) {
System.out.println("moving to ready");
ready = true;
writeTimeToFile("readyAt");
}
return false;
System.out.println("ready: " + ready);
return ready;
}

@Override
public void awaitTermination() {
public void awaitStop() {
try {
server.join();
} catch (InterruptedException ignore) {
@@ -98,9 +102,10 @@ public class HttpProcess implements MonitoredProcess {
}

@Override
public void terminate() {
public void stop() {
try {
if (!server.isStopped()) {
System.out.println("HttpProcess stopping");
server.stop();
writeTimeToFile("terminatedAt");
}

+ 85
- 0
server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java Parādīt failu

@@ -0,0 +1,85 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.process.test;

import org.sonar.process.Monitored;
import org.sonar.process.ProcessEntryPoint;
import org.sonar.process.Lifecycle.State;

public class InfiniteTerminationProcess implements Monitored {

private State state = State.INIT;

private final Thread daemon = new Thread() {
@Override
public void run() {
try {
while (true) {
Thread.sleep(100L);
}
} catch (InterruptedException e) {
// return
}
}
};

/**
* Blocks until started()
*/
@Override
public void start() {
state = State.STARTING;
daemon.start();
state = State.STARTED;
}

@Override
public boolean isReady() {
return state == State.STARTED;
}

@Override
public void awaitStop() {
try {
daemon.join();
} catch (InterruptedException e) {
// interrupted by call to terminate()
}
}

/**
* Blocks until stopped
*/
@Override
public void stop() {
state = State.STOPPING;
try {
daemon.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
state = State.STOPPED;
}

public static void main(String[] args) {
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args);
entryPoint.launch(new InfiniteTerminationProcess());
}
}

+ 5
- 5
server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java Parādīt failu

@@ -19,11 +19,11 @@
*/
package org.sonar.process.test;

import org.sonar.process.MonitoredProcess;
import org.sonar.process.Monitored;
import org.sonar.process.ProcessEntryPoint;
import org.sonar.process.State;
import org.sonar.process.Lifecycle.State;

public class StandardProcess implements MonitoredProcess {
public class StandardProcess implements Monitored {

private State state = State.INIT;

@@ -56,7 +56,7 @@ public class StandardProcess implements MonitoredProcess {
}

@Override
public void awaitTermination() {
public void awaitStop() {
try {
daemon.join();
} catch (InterruptedException e) {
@@ -68,7 +68,7 @@ public class StandardProcess implements MonitoredProcess {
* Blocks until stopped
*/
@Override
public void terminate() {
public void stop() {
state = State.STOPPING;
daemon.interrupt();
state = State.STOPPED;

+ 72
- 77
server/sonar-search/src/main/java/org/sonar/search/SearchServer.java Parādīt failu

@@ -27,7 +27,7 @@ import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
import org.slf4j.LoggerFactory;
import org.sonar.process.MinimumViableSystem;
import org.sonar.process.MonitoredProcess;
import org.sonar.process.Monitored;
import org.sonar.process.ProcessEntryPoint;
import org.sonar.process.ProcessLogging;
import org.sonar.process.Props;
@@ -39,7 +39,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class SearchServer implements MonitoredProcess {
public class SearchServer implements Monitored {

public static final String SONAR_NODE_NAME = "sonar.node.name";
public static final String ES_PORT_PROPERTY = "sonar.search.port";
@@ -55,7 +55,6 @@ public class SearchServer implements MonitoredProcess {

private final Set<String> nodes = new HashSet<String>();
private final Props props;
private final Object lock = new Object();

private Node node;

@@ -71,79 +70,77 @@ public class SearchServer implements MonitoredProcess {

@Override
public void start() {
synchronized (lock) {
Integer port = props.valueAsInt(ES_PORT_PROPERTY);
String clusterName = props.value(ES_CLUSTER_PROPERTY);

LoggerFactory.getLogger(SearchServer.class).info("Starting ES[{}] on port: {}", clusterName, port);

ImmutableSettings.Builder esSettings = ImmutableSettings.settingsBuilder()

// Disable MCast
.put("discovery.zen.ping.multicast.enabled", "false")

// Index storage policies
.put("index.merge.policy.max_merge_at_once", "200")
.put("index.merge.policy.segments_per_tier", "200")
.put("index.number_of_shards", "1")
.put("index.number_of_replicas", MINIMUM_INDEX_REPLICATION)
.put("index.store.type", "mmapfs")
.put("indices.store.throttle.type", "merge")
.put("indices.store.throttle.max_bytes_per_sec", "200mb")

// Install our own listUpdate scripts
.put("script.default_lang", "native")
.put("script.native." + ListUpdate.NAME + ".type", ListUpdate.UpdateListScriptFactory.class.getName())

// Node is pure transport
.put("transport.tcp.port", port)
.put("http.enabled", false)

// Setting up ES paths
.put("path.data", esDataDir().getAbsolutePath())
.put("path.work", esWorkDir().getAbsolutePath())
.put("path.logs", esLogDir().getAbsolutePath());

if (!nodes.isEmpty()) {

LoggerFactory.getLogger(SearchServer.class).info("Joining ES cluster with master: {}", nodes);
esSettings.put("discovery.zen.ping.unicast.hosts", StringUtils.join(nodes, ","));
esSettings.put("node.master", false);
// Enforce a N/2+1 number of masters in cluster
esSettings.put("discovery.zen.minimum_master_nodes", 1);
// Change master pool requirement when in distributed mode
// esSettings.put("discovery.zen.minimum_master_nodes", (int) Math.floor(nodes.size() / 2.0) + 1);
}
Integer port = props.valueAsInt(ES_PORT_PROPERTY);
String clusterName = props.value(ES_CLUSTER_PROPERTY);

LoggerFactory.getLogger(SearchServer.class).info("Starting ES[{}] on port: {}", clusterName, port);

ImmutableSettings.Builder esSettings = ImmutableSettings.settingsBuilder()

// Disable MCast
.put("discovery.zen.ping.multicast.enabled", "false")

// Index storage policies
.put("index.merge.policy.max_merge_at_once", "200")
.put("index.merge.policy.segments_per_tier", "200")
.put("index.number_of_shards", "1")
.put("index.number_of_replicas", MINIMUM_INDEX_REPLICATION)
.put("index.store.type", "mmapfs")
.put("indices.store.throttle.type", "merge")
.put("indices.store.throttle.max_bytes_per_sec", "200mb")

// Install our own listUpdate scripts
.put("script.default_lang", "native")
.put("script.native." + ListUpdate.NAME + ".type", ListUpdate.UpdateListScriptFactory.class.getName())

// Node is pure transport
.put("transport.tcp.port", port)
.put("http.enabled", false)

// Setting up ES paths
.put("path.data", esDataDir().getAbsolutePath())
.put("path.work", esWorkDir().getAbsolutePath())
.put("path.logs", esLogDir().getAbsolutePath());

if (!nodes.isEmpty()) {

LoggerFactory.getLogger(SearchServer.class).info("Joining ES cluster with master: {}", nodes);
esSettings.put("discovery.zen.ping.unicast.hosts", StringUtils.join(nodes, ","));
esSettings.put("node.master", false);
// Enforce a N/2+1 number of masters in cluster
esSettings.put("discovery.zen.minimum_master_nodes", 1);
// Change master pool requirement when in distributed mode
// esSettings.put("discovery.zen.minimum_master_nodes", (int) Math.floor(nodes.size() / 2.0) + 1);
}

// Set cluster coordinates
esSettings.put("cluster.name", clusterName);
esSettings.put("node.rack_id", props.value(SONAR_NODE_NAME, "unknown"));
esSettings.put("cluster.routing.allocation.awareness.attributes", "rack_id");
if (props.contains(SONAR_NODE_NAME)) {
esSettings.put("node.name", props.value(SONAR_NODE_NAME));
} else {
try {
esSettings.put("node.name", InetAddress.getLocalHost().getHostName());
} catch (Exception e) {
LoggerFactory.getLogger(SearchServer.class).warn("Could not determine hostname", e);
esSettings.put("node.name", "sq-" + System.currentTimeMillis());
}
// Set cluster coordinates
esSettings.put("cluster.name", clusterName);
esSettings.put("node.rack_id", props.value(SONAR_NODE_NAME, "unknown"));
esSettings.put("cluster.routing.allocation.awareness.attributes", "rack_id");
if (props.contains(SONAR_NODE_NAME)) {
esSettings.put("node.name", props.value(SONAR_NODE_NAME));
} else {
try {
esSettings.put("node.name", InetAddress.getLocalHost().getHostName());
} catch (Exception e) {
LoggerFactory.getLogger(SearchServer.class).warn("Could not determine hostname", e);
esSettings.put("node.name", "sq-" + System.currentTimeMillis());
}
}

// Make sure the index settings are up to date.
initAnalysis(esSettings);
// Make sure the index settings are up to date.
initAnalysis(esSettings);

// And building our ES Node
node = NodeBuilder.nodeBuilder()
.settings(esSettings)
.build().start();
// And building our ES Node
node = NodeBuilder.nodeBuilder()
.settings(esSettings)
.build().start();

node.client().admin().indices()
.preparePutTemplate("default")
.setTemplate("*")
.addMapping("_default_", "{\"dynamic\": \"strict\"}")
.get();
}
node.client().admin().indices()
.preparePutTemplate("default")
.setTemplate("*")
.addMapping("_default_", "{\"dynamic\": \"strict\"}")
.get();
}

@Override
@@ -156,7 +153,7 @@ public class SearchServer implements MonitoredProcess {
}

@Override
public void awaitTermination() {
public void awaitStop() {
while (node != null && !node.isClosed()) {
try {
Thread.sleep(200L);
@@ -251,11 +248,9 @@ public class SearchServer implements MonitoredProcess {
}

@Override
public void terminate() {
synchronized (lock) {
if (!node.isClosed()) {
node.close();
}
public synchronized void stop() {
if (!node.isClosed()) {
node.close();
}
}


+ 5
- 0
server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java Parādīt failu

@@ -85,10 +85,15 @@ class EmbeddedTomcat {
}

void terminate() {
LoggerFactory.getLogger(getClass()).info("--- RECEIVED TERMINATE TOMCAT");
if (tomcat.getServer().getState().isAvailable()) {
try {
LoggerFactory.getLogger(getClass()).info("--- STOP TOMCAT");
tomcat.stop();
tomcat.destroy();
LoggerFactory.getLogger(getClass()).info("--- TOMCAT TERMINATION - WAIT 10s");
Thread.sleep(10000L);
LoggerFactory.getLogger(getClass()).info("--- TOMCAT TERMINATED AFTER 10s");
} catch (Exception e) {
LoggerFactory.getLogger(EmbeddedTomcat.class).error("Fail to stop web server", e);
}

+ 4
- 4
server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java Parādīt failu

@@ -20,11 +20,11 @@
package org.sonar.server.app;

import org.sonar.process.MinimumViableSystem;
import org.sonar.process.MonitoredProcess;
import org.sonar.process.Monitored;
import org.sonar.process.ProcessEntryPoint;
import org.sonar.process.Props;

public class WebServer implements MonitoredProcess {
public class WebServer implements Monitored {

private final EmbeddedTomcat tomcat;

@@ -46,12 +46,12 @@ public class WebServer implements MonitoredProcess {
}

@Override
public void terminate() {
public void stop() {
tomcat.terminate();
}

@Override
public void awaitTermination() {
public void awaitStop() {
tomcat.awaitTermination();
}


+ 3
- 9
server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java Parādīt failu

@@ -25,10 +25,10 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.config.Settings;
import org.sonar.api.database.DatabaseProperties;
import org.sonar.process.NetworkUtils;

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.sql.DriverManager;

import static junit.framework.Assert.fail;
@@ -41,7 +41,7 @@ public class EmbeddedDatabaseTest {

@Test(timeout = 10000)
public void should_start_and_stop() throws IOException {
int port = freeServerPort();
int port = NetworkUtils.freePort();

EmbeddedDatabase database = new EmbeddedDatabase(testSettings(port));
database.start();
@@ -59,7 +59,7 @@ public class EmbeddedDatabaseTest {

@Test(timeout = 10000)
public void should_support_memory_database() throws IOException {
int port = freeServerPort();
int port = NetworkUtils.freePort();

EmbeddedDatabase database = new EmbeddedDatabase(testSettings(port)
.setProperty(DatabaseProperties.PROP_URL, "jdbc:h2:tcp://localhost:" + port + "/mem:sonarIT;USER=sonar;PASSWORD=sonar"));
@@ -94,10 +94,4 @@ public class EmbeddedDatabaseTest {
.setProperty(DatabaseProperties.PROP_EMBEDDED_PORT, "" + port)
.setProperty("sonar.path.data", "./target/testDB");
}

static int freeServerPort() throws IOException {
ServerSocket srv = new ServerSocket(0);
srv.close();
return srv.getLocalPort();
}
}

+ 1
- 2
server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java Parādīt failu

@@ -29,7 +29,6 @@ import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.config.Settings;
import org.sonar.process.MonitoredProcess;
import org.sonar.process.NetworkUtils;
import org.sonar.process.Props;
import org.sonar.search.SearchServer;
@@ -72,7 +71,7 @@ public class BaseIndexTest {

@AfterClass
public static void teardownSearchEngine() {
searchServer.terminate();
searchServer.stop();
}

@Before

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java Parādīt failu

@@ -139,7 +139,7 @@ public class ServerTester extends ExternalResource {
*/
public void stop() {
platform.doStop();
searchServer.terminate();
searchServer.stop();
FileUtils.deleteQuietly(homeDir);
}


+ 0
- 9
sonar-application/src/main/assembly/conf/sonar.properties Parādīt failu

@@ -69,11 +69,6 @@ sonar.jdbc.timeBetweenEvictionRunsMillis=30000
#sonar.web.javaOpts=-Xmx768m -XX:MaxPermSize=160m -XX:+HeapDumpOnOutOfMemoryError \
# -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djruby.management.enabled=false

# Web server requires a JMX RMI port to be open. Default is 9003. A free port is
# dynamically used is value is 0.
# This JMX port must be private and must not be exposed to the Internet.
#sonar.web.jmxPort=9003

# Binding IP address. For servers with more than one IP address, this property specifies which
# address will be used for listening on the specified ports.
# By default, ports will be used on all IP addresses associated with the server.
@@ -182,10 +177,6 @@ sonar.jdbc.timeBetweenEvictionRunsMillis=30000
# This port must be private and must not be exposed to the Internet.
#sonar.search.port=9001

# JMX RMI port to monitor Elasticsearch process. Default is 9002. Use 0 to get a free port.
# It must be private and must not be exposed to the Internet.
#sonar.search.jmxPort=9002


#--------------------------------------------------------------------------------------------------
# UPDATE CENTER

+ 3
- 24
sonar-application/src/main/java/org/sonar/application/App.java Parādīt failu

@@ -21,12 +21,9 @@ package org.sonar.application;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.process.JmxUtils;
import org.sonar.process.MinimumViableSystem;
import org.sonar.process.ProcessLogging;
import org.sonar.process.ProcessMXBean;
import org.sonar.process.Props;
import org.sonar.process.State;
import org.sonar.process.monitor.JavaCommand;
import org.sonar.process.monitor.Monitor;

@@ -38,7 +35,7 @@ import java.util.Properties;
/**
* Entry-point of process that starts and monitors elasticsearch and web servers
*/
public class App implements ProcessMXBean {
public class App {

private final Monitor monitor;

@@ -48,7 +45,6 @@ public class App implements ProcessMXBean {

App(Monitor monitor) {
this.monitor = monitor;
JmxUtils.registerMBean(this, "SonarQube");
}

public void start(Props props) {
@@ -60,10 +56,9 @@ public class App implements ProcessMXBean {
List<JavaCommand> commands = new ArrayList<JavaCommand>();
File homeDir = props.nonNullValueAsFile("sonar.path.home");
File tempDir = props.nonNullValueAsFile("sonar.path.temp");
JavaCommand elasticsearch = new JavaCommand(JmxUtils.SEARCH_SERVER_NAME);
JavaCommand elasticsearch = new JavaCommand("search");
elasticsearch
.setWorkDir(homeDir)
.setJmxPort(props.valueAsInt(DefaultSettings.SEARCH_JMX_PORT))
.addJavaOptions(props.value(DefaultSettings.SEARCH_JAVA_OPTS))
.setTempDir(tempDir.getAbsoluteFile())
.setClassName("org.sonar.search.SearchServer")
@@ -74,9 +69,8 @@ public class App implements ProcessMXBean {

// do not yet start SQ in cluster mode. See SONAR-5483 & SONAR-5391
if (StringUtils.isEmpty(props.value(DefaultSettings.CLUSTER_MASTER))) {
JavaCommand webServer = new JavaCommand(JmxUtils.WEB_SERVER_NAME)
JavaCommand webServer = new JavaCommand("web")
.setWorkDir(homeDir)
.setJmxPort(props.valueAsInt(DefaultSettings.WEB_JMX_PORT))
.addJavaOptions(props.nonNullValue(DefaultSettings.WEB_JAVA_OPTS))
.setTempDir(tempDir.getAbsoluteFile())
// required for logback tomcat valve
@@ -94,21 +88,6 @@ public class App implements ProcessMXBean {
return commands;
}

@Override
public void terminate() {
monitor.stop();
}

@Override
public boolean isReady() {
return monitor.getState() == State.STARTED;
}

@Override
public void ping() {

}

static String starPath(File homeDir, String relativePath) {
File dir = new File(homeDir, relativePath);
return FilenameUtils.concat(dir.getAbsolutePath(), "*");

+ 0
- 4
sonar-application/src/main/java/org/sonar/application/DefaultSettings.java Parādīt failu

@@ -35,9 +35,7 @@ class DefaultSettings {
static final String CLUSTER_NAME = "sonar.cluster.name";
static final String CLUSTER_NODE_NAME = "sonar.node.name";
static final String SEARCH_PORT = "sonar.search.port";
static final String SEARCH_JMX_PORT = "sonar.search.jmxPort";
static final String SEARCH_JAVA_OPTS = "sonar.search.javaOpts";
static final String WEB_JMX_PORT = "sonar.web.jmxPort";
static final String WEB_JAVA_OPTS = "sonar.web.javaOpts";
static final String JDBC_URL = "sonar.jdbc.url";
static final String JDBC_LOGIN = "sonar.jdbc.username";
@@ -84,8 +82,6 @@ class DefaultSettings {
private static Map<String, Integer> defaultPorts() {
Map<String, Integer> defaults = new HashMap<String, Integer>();
defaults.put(SEARCH_PORT, 9001);
defaults.put(SEARCH_JMX_PORT, 9002);
defaults.put(WEB_JMX_PORT, 9003);
return defaults;
}
}

+ 2
- 4
sonar-application/src/test/java/org/sonar/application/DefaultSettingsTest.java Parādīt failu

@@ -34,8 +34,6 @@ public class DefaultSettingsTest {
DefaultSettings.init(props);

assertThat(props.value("sonar.search.javaOpts")).contains("-Xmx");
assertThat(props.valueAsInt("sonar.web.jmxPort")).isEqualTo(9003);
assertThat(props.valueAsInt("sonar.search.jmxPort")).isEqualTo(9002);
assertThat(props.value("sonar.jdbc.username")).isEqualTo("sonar");
}

@@ -52,10 +50,10 @@ public class DefaultSettingsTest {
@Test
public void use_random_port_if_zero() throws Exception {
Properties p = new Properties();
p.setProperty("sonar.search.jmxPort", "0");
p.setProperty("sonar.search.port", "0");
Props props = new Props(p);

DefaultSettings.init(props);
assertThat(props.valueAsInt("sonar.web.jmxPort")).isGreaterThan(0);
assertThat(props.valueAsInt("sonar.search.port")).isGreaterThan(0);
}
}

Notiek ielāde…
Atcelt
Saglabāt