@@ -11,7 +11,8 @@ | |||
<name>SonarQube :: Server :: Parent</name> | |||
<modules> | |||
<module>process</module> | |||
<module>sonar-process</module> | |||
<module>sonar-process-monitor</module> | |||
<module>sonar-search</module> | |||
<module>sonar-server</module> | |||
<module>sonar-web</module> |
@@ -69,36 +69,19 @@ | |||
<scope>test</scope> | |||
</dependency> | |||
</dependencies> | |||
<!-- | |||
<build> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-dependency-plugin</artifactId> | |||
<version>2.8</version> | |||
<artifactId>maven-jar-plugin</artifactId> | |||
<executions> | |||
<execution> | |||
<id>copy</id> | |||
<phase>process-test-resources</phase> | |||
<goals> | |||
<goal>copy</goal> | |||
<goal>test-jar</goal> | |||
</goals> | |||
<configuration> | |||
<artifactItems> | |||
<artifactItem> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-dummy-app</artifactId> | |||
<version>${project.version}</version> | |||
<type>jar</type> | |||
<outputDirectory>${project.build.testOutputDirectory}</outputDirectory> | |||
<destFileName>sonar-dummy-app.jar</destFileName> | |||
</artifactItem> | |||
</artifactItems> | |||
</configuration> | |||
</execution> | |||
</executions> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
--> | |||
</project> |
@@ -35,27 +35,37 @@ public class ProcessUtils { | |||
// only static stuff | |||
} | |||
/** | |||
* Do not abuse to this method. It uses exceptions to get status. | |||
* @return false if process is null or terminated, else true. | |||
*/ | |||
public static boolean isAlive(@Nullable Process process) { | |||
if (process == null) { | |||
return false; | |||
} | |||
try { | |||
process.exitValue(); | |||
return false; | |||
} catch (IllegalThreadStateException e) { | |||
LOGGER.trace("Process has no exit value yet", e); | |||
return true; | |||
boolean alive = false; | |||
if (process != null) { | |||
try { | |||
process.exitValue(); | |||
} catch (IllegalThreadStateException ignored) { | |||
alive = true; | |||
} | |||
} | |||
return alive; | |||
} | |||
public static void destroyQuietly(@Nullable Process process) { | |||
if (process != null && isAlive(process)) { | |||
/** | |||
* Destroys process (equivalent to kill -9) if alive | |||
* @return true if the process was destroyed, false if process is null or already destroyed. | |||
*/ | |||
public static boolean destroyQuietly(@Nullable Process process) { | |||
boolean destroyed = false; | |||
if (isAlive(process)) { | |||
try { | |||
process.destroy(); | |||
} catch (Exception ignored) { | |||
LOGGER.warn("Exception while destroying the process", ignored); | |||
destroyed = true; | |||
} catch (Exception e) { | |||
LoggerFactory.getLogger(ProcessUtils.class).error("Fail to destroy " + process); | |||
} | |||
} | |||
return destroyed; | |||
} | |||
public static void addSelfShutdownHook(final Terminable terminable) { |
@@ -0,0 +1,23 @@ | |||
/* | |||
* 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.process2; | |||
public class MonitorTest { | |||
} |
@@ -0,0 +1,87 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>server</artifactId> | |||
<version>4.5-SNAPSHOT</version> | |||
<relativePath>../</relativePath> | |||
</parent> | |||
<modelVersion>4.0.0</modelVersion> | |||
<artifactId>sonar-process-monitor</artifactId> | |||
<name>SonarQube :: Process Monitor</name> | |||
<dependencies> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-process</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.slf4j</groupId> | |||
<artifactId>slf4j-api</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>ch.qos.logback</groupId> | |||
<artifactId>logback-classic</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>commons-codec</groupId> | |||
<artifactId>commons-codec</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>commons-io</groupId> | |||
<artifactId>commons-io</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>commons-lang</groupId> | |||
<artifactId>commons-lang</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.google.code.findbugs</groupId> | |||
<artifactId>jsr305</artifactId> | |||
<scope>provided</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>junit</groupId> | |||
<artifactId>junit</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.easytesting</groupId> | |||
<artifactId>fest-assert</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.mockito</groupId> | |||
<artifactId>mockito-core</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.hamcrest</groupId> | |||
<artifactId>hamcrest-all</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.google.guava</groupId> | |||
<artifactId>guava</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-process</artifactId> | |||
<type>test-jar</type> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.github.kevinsawicki</groupId> | |||
<artifactId>http-request</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
</dependencies> | |||
</project> |
@@ -0,0 +1,175 @@ | |||
/* | |||
* 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 javax.annotation.Nullable; | |||
import java.io.File; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.HashMap; | |||
import java.util.LinkedHashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Properties; | |||
public class JavaCommand { | |||
// unique key among the group of commands to launch | |||
private final String key; | |||
private File workDir; | |||
// any available port by default | |||
private int jmxPort = -1; | |||
// for example -Xmx1G | |||
private final List<String> javaOptions = new ArrayList<String>(); | |||
// entry point | |||
private String className; | |||
// relative path to JAR files | |||
private final List<String> classpath = new ArrayList<String>(); | |||
// program arguments (parameters of main(String[]) | |||
private final Map<String, String> arguments = new LinkedHashMap<String, String>(); | |||
private final Map<String, String> envVariables = new HashMap<String, String>(System.getenv()); | |||
public JavaCommand(String key) { | |||
this.key = key; | |||
} | |||
public String getKey() { | |||
return key; | |||
} | |||
public File getWorkDir() { | |||
return workDir; | |||
} | |||
public JavaCommand setWorkDir(File workDir) { | |||
this.workDir = workDir; | |||
return this; | |||
} | |||
public JavaCommand setTempDir(File tempDir) { | |||
this.javaOptions.add("-Djava.io.tmpdir=" + tempDir.getAbsolutePath()); | |||
return this; | |||
} | |||
public int getJmxPort() { | |||
return jmxPort; | |||
} | |||
public JavaCommand setJmxPort(int jmxPort) { | |||
this.jmxPort = jmxPort; | |||
return this; | |||
} | |||
public List<String> getJavaOptions() { | |||
return javaOptions; | |||
} | |||
public JavaCommand addJavaOption(String s) { | |||
javaOptions.add(s); | |||
return this; | |||
} | |||
public JavaCommand addJavaOptions(String s) { | |||
Collections.addAll(javaOptions, s.split(" ")); | |||
return this; | |||
} | |||
public String getClassName() { | |||
return className; | |||
} | |||
public JavaCommand setClassName(String className) { | |||
this.className = className; | |||
return this; | |||
} | |||
public List<String> getClasspath() { | |||
return classpath; | |||
} | |||
public JavaCommand addClasspath(String s) { | |||
classpath.add(s); | |||
return this; | |||
} | |||
public Map<String, String> getArguments() { | |||
return arguments; | |||
} | |||
public JavaCommand setArgument(String key, @Nullable String value) { | |||
if (value == null) { | |||
arguments.remove(key); | |||
} else { | |||
arguments.put(key, value); | |||
} | |||
return this; | |||
} | |||
public JavaCommand setArguments(Properties args) { | |||
for (Map.Entry<Object, Object> entry : args.entrySet()) { | |||
setArgument(entry.getKey().toString(), entry.getValue() != null ? entry.getValue().toString() : null); | |||
} | |||
return this; | |||
} | |||
public Map<String, String> getEnvVariables() { | |||
return envVariables; | |||
} | |||
public JavaCommand setEnvVariable(String key, @Nullable String value) { | |||
if (value == null) { | |||
envVariables.remove(key); | |||
} else { | |||
envVariables.put(key, value); | |||
} | |||
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); | |||
sb.append(", arguments=").append(arguments); | |||
sb.append(", envVariables=").append(envVariables); | |||
sb.append('}'); | |||
return sb.toString(); | |||
} | |||
} |
@@ -0,0 +1,124 @@ | |||
/* | |||
* 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.apache.commons.lang.StringUtils; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.process.LoopbackAddress; | |||
import org.sonar.process.ProcessEntryPoint; | |||
import org.sonar.process.ProcessUtils; | |||
import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.OutputStream; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import java.util.Properties; | |||
public class JavaProcessLauncher { | |||
private final Timeouts timeouts; | |||
public JavaProcessLauncher(Timeouts timeouts) { | |||
this.timeouts = timeouts; | |||
} | |||
ProcessRef launch(JavaCommand command) { | |||
Process process = null; | |||
try { | |||
ProcessBuilder processBuilder = create(command); | |||
LoggerFactory.getLogger(getClass()).info("Launch {}: {}", | |||
command.getKey(), StringUtils.join(processBuilder.command(), " ")); | |||
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); | |||
} catch (Exception e) { | |||
// just in case | |||
ProcessUtils.destroyQuietly(process); | |||
throw new IllegalStateException("Fail to launch " + command.getKey(), e); | |||
} | |||
} | |||
private ProcessBuilder create(JavaCommand javaCommand) { | |||
List<String> commands = new ArrayList<String>(); | |||
commands.add(buildJavaPath()); | |||
commands.addAll(javaCommand.getJavaOptions()); | |||
commands.addAll(buildJmxOptions(javaCommand)); | |||
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()); | |||
return processBuilder; | |||
} | |||
private String buildJavaPath() { | |||
String separator = System.getProperty("file.separator"); | |||
return new File(new File(System.getProperty("java.home")), | |||
"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"))); | |||
} | |||
private File buildPropertiesFile(JavaCommand javaCommand) { | |||
File propertiesFile = null; | |||
try { | |||
propertiesFile = File.createTempFile("sq-conf", "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())); | |||
OutputStream out = new FileOutputStream(propertiesFile); | |||
props.store(out, String.format("Temporary properties file for command [%s]", javaCommand.getKey())); | |||
out.close(); | |||
return propertiesFile; | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Cannot write temporary settings to " + propertiesFile, e); | |||
} | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
/* | |||
* 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 { | |||
void connect(JavaCommand command, ProcessRef processRef); | |||
void ping(ProcessRef process); | |||
boolean isReady(ProcessRef process); | |||
void terminate(ProcessRef process); | |||
} |
@@ -0,0 +1,208 @@ | |||
/* | |||
* 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.Lifecycle; | |||
import org.sonar.process.MessageException; | |||
import org.sonar.process.State; | |||
import org.sonar.process.SystemExit; | |||
import java.util.List; | |||
import java.util.concurrent.CopyOnWriteArrayList; | |||
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 | |||
private final List<WatcherThread> watcherThreads = new CopyOnWriteArrayList<WatcherThread>(); | |||
Monitor(JavaProcessLauncher launcher, JmxConnector jmxConnector, Timeouts timeouts, SystemExit exit) { | |||
this.launcher = launcher; | |||
this.jmxConnector = jmxConnector; | |||
this.timeouts = timeouts; | |||
this.terminator = new TerminatorThread(processes, jmxConnector, timeouts); | |||
this.systemExit = exit; | |||
} | |||
public static Monitor create() { | |||
Timeouts timeouts = new Timeouts(); | |||
return new Monitor(new JavaProcessLauncher(timeouts), new RmiJmxConnector(timeouts), | |||
timeouts, new SystemExit()); | |||
} | |||
/** | |||
* Starts commands and blocks current thread until all processes are in state {@link State#STARTED}. | |||
* @throws java.lang.IllegalArgumentException if commands list is empty | |||
* @throws java.lang.IllegalStateException if already started or if at least one process failed to start. In this case | |||
* all processes are terminated. No need to execute {@link #stop()} | |||
*/ | |||
public void start(List<JavaCommand> commands) { | |||
if (commands.isEmpty()) { | |||
throw new IllegalArgumentException("At least one command is required"); | |||
} | |||
if (!lifecycle.tryToMoveTo(State.STARTING)) { | |||
throw new IllegalStateException("Can not start multiple times"); | |||
} | |||
// intercepts CTRL-C | |||
Runtime.getRuntime().addShutdownHook(shutdownHook); | |||
for (JavaCommand command : commands) { | |||
try { | |||
ProcessRef processRef = launcher.launch(command); | |||
monitor(command, processRef); | |||
} catch (RuntimeException e) { | |||
// fail to start or to monitor | |||
stop(); | |||
throw e; | |||
} | |||
} | |||
if (!lifecycle.tryToMoveTo(State.STARTED)) { | |||
// stopping or stopped during startup, for instance : | |||
// 1. A is started | |||
// 2. B starts | |||
// 3. A crashes while B is starting | |||
// 4. if B was not monitored during Terminator execution, then it's an alive orphan | |||
stop(); | |||
throw new IllegalStateException("Stopped during startup"); | |||
} | |||
} | |||
private void monitor(JavaCommand command, ProcessRef processRef) { | |||
// physically watch if process is alive | |||
WatcherThread watcherThread = new WatcherThread(processRef, this); | |||
watcherThread.start(); | |||
watcherThreads.add(watcherThread); | |||
// add to list of monitored processes only when successfully connected to it | |||
jmxConnector.connect(command, processRef); | |||
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); | |||
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); | |||
} catch (Exception ignored) { | |||
// pb with the JMX connection, can occur if RMI not initialized yet | |||
} | |||
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 | |||
*/ | |||
public void awaitTermination() { | |||
for (WatcherThread watcherThread : watcherThreads) { | |||
while (watcherThread.isAlive()) { | |||
try { | |||
watcherThread.join(); | |||
} catch (InterruptedException ignored) { | |||
// ignore, stop blocking | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Blocks until all processes are terminated. | |||
*/ | |||
public void stop() { | |||
terminateAsync(); | |||
try { | |||
terminator.join(); | |||
} catch (InterruptedException ignored) { | |||
// ignore, stop blocking | |||
} | |||
// safeguard if TerminatorThread is buggy | |||
hardKillAll(); | |||
lifecycle.tryToMoveTo(State.STOPPED); | |||
systemExit.exit(0); | |||
} | |||
/** | |||
* 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 requested = false; | |||
if (lifecycle.tryToMoveTo(State.STOPPING)) { | |||
requested = true; | |||
terminator.start(); | |||
} | |||
return requested; | |||
} | |||
private void hardKillAll() { | |||
// no specific order, kill'em all!!! | |||
for (ProcessRef process : processes) { | |||
process.hardKill(); | |||
} | |||
} | |||
public State getState() { | |||
return lifecycle.getState(); | |||
} | |||
Thread getShutdownHook() { | |||
return shutdownHook; | |||
} | |||
private class MonitorShutdownHook implements Runnable { | |||
@Override | |||
public void run() { | |||
systemExit.setInShutdownHook(); | |||
// blocks until everything is corrected terminated | |||
stop(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
/* | |||
* 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); | |||
} | |||
} |
@@ -0,0 +1,98 @@ | |||
/* | |||
* 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.sonar.process.ProcessUtils; | |||
class ProcessRef { | |||
private final String key; | |||
private final Process process; | |||
private final StreamGobbler[] gobblers; | |||
private volatile boolean terminated = false; | |||
private volatile boolean pingEnabled = true; | |||
ProcessRef(String key, Process process, StreamGobbler... gobblers) { | |||
this.key = key; | |||
this.process = process; | |||
this.terminated = !ProcessUtils.isAlive(process); | |||
this.gobblers = gobblers; | |||
} | |||
/** | |||
* Unique logical key (not the pid), for instance "ES" | |||
*/ | |||
String getKey() { | |||
return key; | |||
} | |||
/** | |||
* The {@link java.lang.Process} | |||
*/ | |||
Process getProcess() { | |||
return process; | |||
} | |||
/** | |||
* Almost real-time status | |||
*/ | |||
boolean isTerminated() { | |||
return terminated; | |||
} | |||
/** | |||
* Sending pings can be disabled when requesting for termination or when process is on debug mode (JDWP) | |||
*/ | |||
void setPingEnabled(boolean b) { | |||
this.pingEnabled = b; | |||
} | |||
boolean isPingEnabled() { | |||
return pingEnabled; | |||
} | |||
/** | |||
* Destroy the process without gracefully asking it to terminate (kill -9). | |||
* @return true if the process was killed, false if process is already terminated | |||
*/ | |||
boolean hardKill() { | |||
boolean killed = false; | |||
terminated = true; | |||
pingEnabled = false; | |||
if (ProcessUtils.isAlive(process)) { | |||
ProcessUtils.destroyQuietly(process); | |||
killed = true; | |||
} | |||
for (StreamGobbler gobbler : gobblers) { | |||
StreamGobbler.waitUntilFinish(gobbler); | |||
} | |||
ProcessUtils.closeStreams(process); | |||
return killed; | |||
} | |||
void setTerminated(boolean b) { | |||
this.terminated = b; | |||
} | |||
@Override | |||
public String toString() { | |||
return String.format("Process[%s]", key); | |||
} | |||
} |
@@ -0,0 +1,133 @@ | |||
/* | |||
* 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.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>(); | |||
private final Timeouts timeouts; | |||
RmiJmxConnector(Timeouts timeouts) { | |||
this.timeouts = timeouts; | |||
} | |||
@Override | |||
public synchronized void connect(final JavaCommand command, ProcessRef processRef) { | |||
ExecutorService executor = Executors.newSingleThreadExecutor(); | |||
ConnectorCallable callable = new ConnectorCallable(command, processRef.getProcess()); | |||
try { | |||
Future<ProcessMXBean> future = executor.submit(callable); | |||
ProcessMXBean mxBean = future.get(timeouts.getJmxConnectionTimeout(), TimeUnit.MILLISECONDS); | |||
if (mxBean != null) { | |||
mbeans.put(processRef, mxBean); | |||
} | |||
} catch (Exception e) { | |||
if (callable.latestException != null) { | |||
throw callable.latestException; | |||
} | |||
throw new IllegalStateException("Fail to connect to JMX", e); | |||
} finally { | |||
executor.shutdownNow(); | |||
} | |||
} | |||
@Override | |||
public void ping(ProcessRef processRef) { | |||
mbeans.get(processRef).ping(); | |||
} | |||
@Override | |||
public boolean isReady(ProcessRef processRef) { | |||
return mbeans.get(processRef).isReady(); | |||
} | |||
@Override | |||
public void terminate(ProcessRef processRef) { | |||
mbeans.get(processRef).terminate(); | |||
} | |||
private static class ConnectorCallable implements Callable<ProcessMXBean> { | |||
private final JavaCommand command; | |||
private final Process process; | |||
private RuntimeException latestException; | |||
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 e) { | |||
latestException = new IllegalStateException(String.format( | |||
"Fail to connect to JMX bean of %s [%s] ", command.getKey(), jmxUrl), e); | |||
} | |||
Thread.sleep(300L); | |||
} | |||
// process went down, no need to connect | |||
return null; | |||
} | |||
} | |||
} |
@@ -0,0 +1,69 @@ | |||
/* | |||
* 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.apache.commons.io.IOUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import javax.annotation.Nullable; | |||
import java.io.BufferedReader; | |||
import java.io.InputStream; | |||
import java.io.InputStreamReader; | |||
/** | |||
* Reads process output and writes to logs | |||
*/ | |||
class StreamGobbler extends Thread { | |||
private final InputStream is; | |||
private final Logger logger; | |||
StreamGobbler(InputStream is, String processKey) { | |||
super(String.format("Gobbler[%s]", processKey)); | |||
this.is = is; | |||
this.logger = LoggerFactory.getLogger(processKey); | |||
} | |||
@Override | |||
public void run() { | |||
BufferedReader br = new BufferedReader(new InputStreamReader(is)); | |||
try { | |||
String line; | |||
while ((line = br.readLine()) != null) { | |||
logger.info(line); | |||
} | |||
} catch (Exception ignored) { | |||
} finally { | |||
IOUtils.closeQuietly(br); | |||
} | |||
} | |||
static void waitUntilFinish(@Nullable StreamGobbler gobbler) { | |||
if (gobbler != null) { | |||
try { | |||
gobbler.join(); | |||
} catch (InterruptedException ignored) { | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,78 @@ | |||
/* | |||
* 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 java.util.List; | |||
import java.util.concurrent.ExecutorService; | |||
import java.util.concurrent.Executors; | |||
import java.util.concurrent.Future; | |||
import java.util.concurrent.TimeUnit; | |||
/** | |||
* Terminates all monitored processes. Tries to gracefully terminate each process, | |||
* then kill if timeout expires. Ping monitoring is disabled so process auto kills (self graceful termination, else self kill) | |||
* if it does not receive the termination request. | |||
*/ | |||
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) { | |||
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); | |||
ExecutorService executor = Executors.newSingleThreadExecutor(); | |||
Future future = executor.submit(new Runnable() { | |||
@Override | |||
public void run() { | |||
// ask for graceful termination | |||
LoggerFactory.getLogger(getClass()).info("Request termination of " + processRef); | |||
jmxConnector.terminate(processRef); | |||
} | |||
}); | |||
try { | |||
future.get(timeouts.getTerminationTimeout(), TimeUnit.MILLISECONDS); | |||
} catch (Exception ignored) { | |||
// failed to gracefully stop in a timely fashion | |||
LoggerFactory.getLogger(getClass()).info(String.format("Kill %s", processRef)); | |||
} finally { | |||
executor.shutdownNow(); | |||
// kill even if graceful termination was done, just to be sure that physical process is really down | |||
processRef.hardKill(); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,103 @@ | |||
/* | |||
* 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; | |||
/** | |||
* Most of the timeouts involved in process monitoring, in milliseconds | |||
*/ | |||
class Timeouts { | |||
private long terminationTimeout = 120000L; | |||
private long jmxConnectionTimeout = 30000L; | |||
private long monitorPingInterval = 3000L; | |||
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; | |||
} | |||
/** | |||
* [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 | |||
*/ | |||
long getTerminationTimeout() { | |||
return terminationTimeout; | |||
} | |||
/** | |||
* @see #getTerminationTimeout() | |||
*/ | |||
void setTerminationTimeout(long l) { | |||
this.terminationTimeout = l; | |||
} | |||
} |
@@ -0,0 +1,70 @@ | |||
/* | |||
* 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.ProcessUtils; | |||
/** | |||
* This thread blocks as long as the monitored process is physically alive. | |||
* It avoids from executing {@link Process#exitValue()} at a fixed rate : | |||
* <ul> | |||
* <li>no usage of exception for flow control. Indeed {@link Process#exitValue()} throws an exception | |||
* if process is alive. There's no method <code>Process#isAlive()</code></li> | |||
* <li>no delay, instantaneous notification that process is down</li> | |||
* </ul> | |||
*/ | |||
class WatcherThread extends Thread { | |||
private final ProcessRef process; | |||
private final Monitor monitor; | |||
WatcherThread(ProcessRef processRef, Monitor monitor) { | |||
// this name is different than Thread#toString(), which includes name, priority | |||
// and thread group | |||
// -> do not override toString() | |||
super(String.format("Watch[%s]", processRef.getKey())); | |||
this.process = processRef; | |||
this.monitor = monitor; | |||
} | |||
@Override | |||
public void run() { | |||
boolean alive = true; | |||
while (alive) { | |||
try { | |||
process.getProcess().waitFor(); | |||
process.setTerminated(true); | |||
LoggerFactory.getLogger(getClass()).info(process + " is down"); | |||
// terminate all other processes, but in another thread | |||
monitor.stop(); | |||
alive = false; | |||
} catch (InterruptedException ignored) { | |||
if (ProcessUtils.isAlive(process.getProcess())) { | |||
LoggerFactory.getLogger(getClass()).error(String.format( | |||
"Watcher of [%s] was interrupted but process is still alive. Killing it.", process.getKey())); | |||
} | |||
alive = false; | |||
} finally { | |||
process.hardKill(); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
/* | |||
* 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. | |||
*/ | |||
@ParametersAreNonnullByDefault | |||
package org.sonar.process.monitor; | |||
import javax.annotation.ParametersAreNonnullByDefault; |
@@ -0,0 +1,38 @@ | |||
/* | |||
* 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; | |||
CallVerifierJmxConnector(Timeouts timeouts) { | |||
super(timeouts); | |||
} | |||
@Override | |||
public void ping(ProcessRef process) { | |||
askedPing = true; | |||
super.ping(process); | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
/* | |||
* 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) { | |||
throw new IllegalStateException("Test - Impossible to connect to JMX"); | |||
} | |||
@Override | |||
public void ping(ProcessRef process) { | |||
} | |||
@Override | |||
public boolean isReady(ProcessRef process) { | |||
return false; | |||
} | |||
@Override | |||
public void terminate(ProcessRef process) { | |||
} | |||
} |
@@ -0,0 +1,38 @@ | |||
/* | |||
* 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 InfiniteTerminationRmiConnector extends RmiJmxConnector { | |||
InfiniteTerminationRmiConnector(Timeouts timeouts) { | |||
super(timeouts); | |||
} | |||
@Override | |||
public void terminate(ProcessRef processRef) { | |||
try { | |||
while (true) { | |||
Thread.sleep(50L); | |||
} | |||
} catch (Exception e) { | |||
} | |||
} | |||
} |
@@ -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.monitor; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import java.io.File; | |||
import java.util.Properties; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
public class JavaCommandTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@Test | |||
public void test_parameters() throws Exception { | |||
JavaCommand command = new JavaCommand("es"); | |||
command.setArgument("first_arg", "val1"); | |||
Properties args = new Properties(); | |||
args.setProperty("second_arg", "val2"); | |||
command.setArguments(args); | |||
command.setJmxPort(1234); | |||
command.setClassName("org.sonar.ElasticSearch"); | |||
command.setEnvVariable("BUILD_ID", "1000"); | |||
File tempDir = temp.newFolder(); | |||
command.setTempDir(tempDir); | |||
File workDir = temp.newFolder(); | |||
command.setWorkDir(workDir); | |||
command.addClasspath("lib/*.jar"); | |||
command.addClasspath("conf/*.xml"); | |||
command.addJavaOption("-Xmx128m"); | |||
assertThat(command.toString()).isNotNull(); | |||
assertThat(command.getClasspath()).containsOnly("lib/*.jar", "conf/*.xml"); | |||
assertThat(command.getJavaOptions()).containsOnly("-Xmx128m", "-Djava.io.tmpdir=" + tempDir.getAbsolutePath()); | |||
assertThat(command.getWorkDir()).isSameAs(workDir); | |||
assertThat(command.getJmxPort()).isEqualTo(1234); | |||
assertThat(command.getClassName()).isEqualTo("org.sonar.ElasticSearch"); | |||
assertThat(command.getEnvVariables().get("BUILD_ID")).isEqualTo("1000"); | |||
// copy current env variables | |||
assertThat(command.getEnvVariables().size()).isGreaterThan(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(); | |||
} | |||
} |
@@ -0,0 +1,43 @@ | |||
/* | |||
* 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.sonar.process.NetworkUtils; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.fest.assertions.Fail.fail; | |||
public class JavaProcessLauncherTest { | |||
@Test | |||
public void fail_to_launch() throws Exception { | |||
JavaCommand command = new JavaCommand("test").setJmxPort(NetworkUtils.freePort()); | |||
JavaProcessLauncher launcher = new JavaProcessLauncher(new Timeouts()); | |||
try { | |||
// command is not correct (missing options), java.lang.ProcessBuilder#start() | |||
// throws an exception | |||
launcher.launch(command); | |||
fail(); | |||
} catch (IllegalStateException e) { | |||
assertThat(e).hasMessage("Fail to launch test"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,442 @@ | |||
/* | |||
* 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 com.github.kevinsawicki.http.HttpRequest; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.After; | |||
import org.junit.BeforeClass; | |||
import org.junit.Rule; | |||
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.SystemExit; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.net.ConnectException; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.fest.assertions.Fail.fail; | |||
import static org.mockito.Mockito.mock; | |||
public class MonitorTest { | |||
static File testJar; | |||
Monitor monitor; | |||
SystemExit exit = mock(SystemExit.class); | |||
/** | |||
* Find the JAR file containing the test apps. Classes can't be moved in sonar-process-monitor because | |||
* they require sonar-process dependencies when executed here (sonar-process, commons-*, ...). | |||
*/ | |||
@BeforeClass | |||
public static void initTestJar() { | |||
File targetDir = new File("server/sonar-process/target"); | |||
if (!targetDir.exists() || !targetDir.isDirectory()) { | |||
targetDir = new File("../sonar-process/target"); | |||
} | |||
if (!targetDir.exists() || !targetDir.isDirectory()) { | |||
throw new IllegalStateException("target dir of sonar-process module not found. Please build it."); | |||
} | |||
Collection<File> jars = FileUtils.listFiles(targetDir, new String[] {"jar"}, false); | |||
for (File jar : jars) { | |||
if (jar.getName().startsWith("sonar-process-") && jar.getName().endsWith("-test-jar-with-dependencies.jar")) { | |||
testJar = jar; | |||
return; | |||
} | |||
} | |||
throw new IllegalStateException("No sonar-process-*-test-jar-with-dependencies.jar in " + targetDir); | |||
} | |||
/** | |||
* Safeguard | |||
*/ | |||
@Rule | |||
public Timeout globalTimeout = new Timeout(10000); | |||
/** | |||
* Temporary directory is used to interact with monitored processes, which write in it. | |||
*/ | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
/** | |||
* Safeguard | |||
*/ | |||
@After | |||
public void tearDown() throws Exception { | |||
try { | |||
if (monitor != null) { | |||
monitor.stop(); | |||
} | |||
} catch (Throwable ignored) { | |||
} | |||
} | |||
@Test | |||
public void fail_to_start_if_no_commands() throws Exception { | |||
monitor = newDefaultMonitor(); | |||
try { | |||
monitor.start(Collections.<JavaCommand>emptyList()); | |||
fail(); | |||
} catch (IllegalArgumentException e) { | |||
assertThat(e).hasMessage("At least one command is required"); | |||
} | |||
} | |||
@Test | |||
public void fail_to_start_multiple_times() throws Exception { | |||
monitor = newDefaultMonitor(); | |||
monitor.start(Arrays.asList(newStandardProcessCommand())); | |||
boolean failed = false; | |||
try { | |||
monitor.start(Arrays.asList(newStandardProcessCommand())); | |||
} catch (IllegalStateException e) { | |||
failed = e.getMessage().equals("Can not start multiple times"); | |||
} | |||
monitor.stop(); | |||
assertThat(failed); | |||
} | |||
@Test | |||
public void start_then_stop_gracefully() throws Exception { | |||
monitor = newDefaultMonitor(); | |||
HttpProcessClient client = new HttpProcessClient("test"); | |||
// blocks until started | |||
monitor.start(Arrays.asList(client.newCommand())); | |||
assertThat(client.isReady()).isTrue(); | |||
assertThat(client.wasReadyAt()).isLessThanOrEqualTo(System.currentTimeMillis()); | |||
// blocks until stopped | |||
monitor.stop(); | |||
assertThat(client.isReady()).isFalse(); | |||
assertThat(client.wasGracefullyTerminated()).isTrue(); | |||
assertThat(monitor.getState()).isEqualTo(State.STOPPED); | |||
} | |||
@Test | |||
public void start_then_stop_sequence_of_commands() throws Exception { | |||
monitor = newDefaultMonitor(); | |||
HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2"); | |||
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand())); | |||
// start p2 when p1 is fully started (ready) | |||
assertThat(p1.isReady()).isTrue(); | |||
assertThat(p2.isReady()).isTrue(); | |||
assertThat(p2.wasStartingAt()).isGreaterThanOrEqualTo(p1.wasReadyAt()); | |||
monitor.stop(); | |||
// stop in inverse order | |||
assertThat(p1.isReady()).isFalse(); | |||
assertThat(p2.isReady()).isFalse(); | |||
assertThat(p1.wasGracefullyTerminated()).isTrue(); | |||
assertThat(p2.wasGracefullyTerminated()).isTrue(); | |||
assertThat(p2.wasGracefullyTerminatedAt()).isLessThanOrEqualTo(p1.wasGracefullyTerminatedAt()); | |||
} | |||
@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 { | |||
monitor = newDefaultMonitor(); | |||
HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2"); | |||
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand())); | |||
assertThat(p1.isReady()).isTrue(); | |||
assertThat(p2.isReady()).isTrue(); | |||
// emulate CTRL-C | |||
monitor.getShutdownHook().run(); | |||
monitor.getShutdownHook().join(); | |||
assertThat(p1.wasGracefullyTerminated()).isTrue(); | |||
assertThat(p2.wasGracefullyTerminated()).isTrue(); | |||
} | |||
@Test | |||
public void terminate_all_processes_if_one_monitored_process_shutdowns() throws Exception { | |||
monitor = newDefaultMonitor(); | |||
HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2"); | |||
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand())); | |||
assertThat(p1.isReady()).isTrue(); | |||
assertThat(p2.isReady()).isTrue(); | |||
// kill p1 -> waiting for detection by monitor than termination of p2 | |||
p1.kill(); | |||
monitor.awaitTermination(); | |||
assertThat(p1.isReady()).isFalse(); | |||
assertThat(p2.isReady()).isFalse(); | |||
assertThat(p1.wasGracefullyTerminated()).isFalse(); | |||
assertThat(p2.wasGracefullyTerminated()).isTrue(); | |||
} | |||
@Test | |||
public void terminate_all_processes_if_one_fails_to_start() throws Exception { | |||
monitor = newDefaultMonitor(); | |||
HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2", -1, NetworkUtils.freePort()); | |||
try { | |||
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand())); | |||
fail(); | |||
} catch (Exception expected) { | |||
assertThat(p1.wasReady()).isTrue(); | |||
assertThat(p2.wasReady()).isFalse(); | |||
assertThat(p1.wasGracefullyTerminated()).isTrue(); | |||
// self "gracefully terminated", even if startup went bad | |||
assertThat(p2.wasGracefullyTerminated()).isTrue(); | |||
} | |||
} | |||
@Test | |||
public void kill_process_if_too_long_to_request_gracefully_termination() throws Exception { | |||
Timeouts timeouts = new Timeouts(); | |||
timeouts.setTerminationTimeout(100L); | |||
monitor = new Monitor(new JavaProcessLauncher(timeouts), | |||
new InfiniteTerminationRmiConnector(timeouts), timeouts, exit); | |||
HttpProcessClient p1 = new HttpProcessClient("p1"); | |||
monitor.start(Arrays.asList(p1.newCommand())); | |||
assertThat(p1.isReady()).isTrue(); | |||
monitor.stop(); | |||
assertThat(p1.isReady()).isFalse(); | |||
} | |||
@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), timeouts, exit); | |||
HttpProcessClient p1 = new HttpProcessClient("p1"); | |||
monitor.start(Arrays.asList(p1.newCommand())); | |||
assertThat(p1.isReady()).isTrue(); | |||
monitor.stop(); | |||
assertThat(p1.isReady()).isFalse(); | |||
} | |||
@Test | |||
public void fail_to_start_if_bad_class_name() throws Exception { | |||
monitor = newDefaultMonitor(); | |||
JavaCommand command = new JavaCommand("test") | |||
.addClasspath(testJar.getAbsolutePath()) | |||
.setClassName("org.sonar.process.test.Unknown") | |||
.setJmxPort(NetworkUtils.freePort()) | |||
.setTempDir(temp.newFolder()); | |||
try { | |||
monitor.start(Arrays.asList(command)); | |||
fail(); | |||
} catch (Exception e) { | |||
// expected | |||
// TODO improve, too many stacktraces logged | |||
} | |||
} | |||
@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(timeouts); | |||
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), timeouts, exit); | |||
} | |||
/** | |||
* Interaction with {@link org.sonar.process.test.HttpProcess} | |||
*/ | |||
private class HttpProcessClient { | |||
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()); | |||
} | |||
/** | |||
* Use httpPort=-1 to make server fail to start | |||
*/ | |||
private HttpProcessClient(String commandKey, int httpPort, int jmxPort) 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); | |||
} | |||
/** | |||
* @see org.sonar.process.test.HttpProcess | |||
*/ | |||
boolean isReady() { | |||
try { | |||
HttpRequest httpRequest = HttpRequest.get("http://localhost:" + httpPort + "/ping") | |||
.readTimeout(500).connectTimeout(500); | |||
return httpRequest.ok() && httpRequest.body().equals("ping"); | |||
} catch (HttpRequest.HttpRequestException e) { | |||
if (e.getCause() instanceof ConnectException) { | |||
return false; | |||
} | |||
throw new IllegalStateException("Fail to know the process status", e); | |||
} | |||
} | |||
/** | |||
* @see org.sonar.process.test.HttpProcess | |||
*/ | |||
void kill() { | |||
try { | |||
HttpRequest.post("http://localhost:" + httpPort + "/kill") | |||
.readTimeout(500).connectTimeout(500).ok(); | |||
} catch (Exception e) { | |||
// HTTP request can't be fully processed, as web server hardly | |||
// calls "System.exit()" | |||
} | |||
} | |||
/** | |||
* @see org.sonar.process.test.HttpProcess | |||
*/ | |||
boolean wasGracefullyTerminated() { | |||
return fileExists("terminatedAt"); | |||
} | |||
long wasStartingAt() throws IOException { | |||
return readTimeFromFile("startingAt"); | |||
} | |||
long wasGracefullyTerminatedAt() throws IOException { | |||
return readTimeFromFile("terminatedAt"); | |||
} | |||
boolean wasReady() throws IOException { | |||
return fileExists("readyAt"); | |||
} | |||
long wasReadyAt() throws IOException { | |||
return readTimeFromFile("readyAt"); | |||
} | |||
private long readTimeFromFile(String filename) throws IOException { | |||
File file = new File(tempDir, filename); | |||
if (file.isFile() && file.exists()) { | |||
return Long.parseLong(FileUtils.readFileToString(file)); | |||
} | |||
throw new IllegalStateException("File does not exist"); | |||
} | |||
private boolean fileExists(String filename) { | |||
File file = new File(tempDir, filename); | |||
return file.isFile() && file.exists(); | |||
} | |||
} | |||
private JavaCommand newStandardProcessCommand() throws IOException { | |||
return new JavaCommand("standard") | |||
.addClasspath(testJar.getAbsolutePath()) | |||
.setClassName("org.sonar.process.test.StandardProcess") | |||
.setJmxPort(NetworkUtils.freePort()) | |||
.setTempDir(temp.newFolder()); | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
/* | |||
* 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 { | |||
TerminationFailureRmiConnector(Timeouts timeouts) { | |||
super(timeouts); | |||
} | |||
@Override | |||
public void terminate(ProcessRef processRef) { | |||
throw new IllegalStateException("Test - fail to send termination request"); | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
/* | |||
* 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 static org.fest.assertions.Assertions.assertThat; | |||
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); | |||
} | |||
@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); | |||
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); | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
/* | |||
* 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.Mockito; | |||
import org.mockito.invocation.InvocationOnMock; | |||
import org.mockito.stubbing.Answer; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.when; | |||
public class WatcherThreadTest { | |||
@Test(timeout = 10000L) | |||
public void kill_process_if_watcher_is_interrupted() throws Exception { | |||
ProcessRef ref = mock(ProcessRef.class, Mockito.RETURNS_DEEP_STUBS); | |||
when(ref.getProcess().waitFor()).thenAnswer(new Answer<Object>() { | |||
@Override | |||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable { | |||
Thread.sleep(Long.MAX_VALUE); | |||
return 0; | |||
} | |||
}); | |||
Monitor monitor = mock(Monitor.class); | |||
WatcherThread watcher = new WatcherThread(ref, monitor); | |||
watcher.start(); | |||
Thread.sleep(50L); | |||
watcher.interrupt(); | |||
verify(ref).hardKill(); | |||
} | |||
} |
@@ -0,0 +1 @@ | |||
0PZz+G+f8mjr3sPn4+AhHg== |
@@ -0,0 +1 @@ | |||
badbadbad== |
@@ -0,0 +1,3 @@ | |||
0PZz+G+f8mjr3sPn4+AhHg== | |||
@@ -0,0 +1 @@ | |||
IBxEUxZ41c8XTxyaah1Qlg== |
@@ -0,0 +1 @@ | |||
<configuration/> |
@@ -0,0 +1,212 @@ | |||
# This file must contain only ISO 8859-1 characters | |||
# see http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Properties.html#load(java.io.InputStream) | |||
# | |||
# To use an environment variable, use the following syntax : ${env:NAME_OF_ENV_VARIABLE} | |||
# For example: | |||
# sonar.jdbc.url= ${env:SONAR_JDBC_URL} | |||
# | |||
# | |||
# See also the file conf/wrapper.conf for JVM advanced settings | |||
#-------------------------------------------------------------------------------------------------- | |||
# DATABASE | |||
# | |||
# IMPORTANT: the embedded H2 database is used by default. It is recommended for tests only. | |||
# Please use a production-ready database. Supported databases are MySQL, Oracle, PostgreSQL | |||
# and Microsoft SQLServer. | |||
# Permissions to create tables, indices and triggers must be granted to JDBC user. | |||
# The schema must be created first. | |||
sonar.jdbc.username=sonar | |||
sonar.jdbc.password=sonar | |||
#----- Embedded database H2 | |||
# Note: it does not accept connections from remote hosts, so the | |||
# SonarQube server and the maven plugin must be executed on the same host. | |||
# Comment the following line to deactivate the default embedded database. | |||
sonar.jdbc.url=jdbc:h2:tcp://localhost:9092/sonar | |||
# directory containing H2 database files. By default it's the /data directory in the SonarQube installation. | |||
#sonar.embeddedDatabase.dataDir= | |||
# H2 embedded database server listening port, defaults to 9092 | |||
#sonar.embeddedDatabase.port=9092 | |||
#----- MySQL 5.x | |||
# Comment the embedded database and uncomment the following line to use MySQL | |||
#sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true | |||
#----- Oracle 10g/11g | |||
# To connect to Oracle database: | |||
# | |||
# - It's recommended to use the latest version of the JDBC driver (ojdbc6.jar). | |||
# Download it in http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html | |||
# - Copy the driver to the directory extensions/jdbc-driver/oracle/ | |||
# - If you need to set the schema, please refer to http://jira.codehaus.org/browse/SONAR-5000 | |||
# - Comment the embedded database and uncomment the following line: | |||
#sonar.jdbc.url=jdbc:oracle:thin:@localhost/XE | |||
#----- PostgreSQL 8.x/9.x | |||
# Comment the embedded database and uncomment the following property to use PostgreSQL. | |||
# If you don't use the schema named "public", please refer to http://jira.codehaus.org/browse/SONAR-5000 | |||
#sonar.jdbc.url=jdbc:postgresql://localhost/sonar | |||
#----- Microsoft SQLServer | |||
# The Jtds open source driver is available in extensions/jdbc-driver/mssql. More details on http://jtds.sourceforge.net | |||
#sonar.jdbc.url=jdbc:jtds:sqlserver://localhost/sonar;SelectMethod=Cursor | |||
#----- Connection pool settings | |||
sonar.jdbc.maxActive=20 | |||
sonar.jdbc.maxIdle=5 | |||
sonar.jdbc.minIdle=2 | |||
sonar.jdbc.maxWait=5000 | |||
sonar.jdbc.minEvictableIdleTimeMillis=600000 | |||
sonar.jdbc.timeBetweenEvictionRunsMillis=30000 | |||
#-------------------------------------------------------------------------------------------------- | |||
# WEB SERVER | |||
# 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. | |||
#sonar.web.host=0.0.0.0 | |||
# Web context. When set, it must start with forward slash (for example /sonarqube). | |||
# The default value is root context (empty value). | |||
#sonar.web.context= | |||
# TCP port for incoming HTTP connections. Disabled when value is -1. | |||
#sonar.web.port=9000 | |||
# TCP port for incoming HTTPS connections. Disabled when value is -1 (default). | |||
#sonar.web.https.port=-1 | |||
# HTTPS - the alias used to for the server certificate in the keystore. | |||
# If not specified the first key read in the keystore is used. | |||
#sonar.web.https.keyAlias= | |||
# HTTPS - the password used to access the server certificate from the | |||
# specified keystore file. The default value is "changeit". | |||
#sonar.web.https.keyPass=changeit | |||
# HTTPS - the pathname of the keystore file where is stored the server certificate. | |||
# By default, the pathname is the file ".keystore" in the user home. | |||
# If keystoreType doesn't need a file use empty value. | |||
#sonar.web.https.keystoreFile= | |||
# HTTPS - the password used to access the specified keystore file. The default | |||
# value is the value of sonar.web.https.keyPass. | |||
#sonar.web.https.keystorePass= | |||
# HTTPS - the type of keystore file to be used for the server certificate. | |||
# The default value is JKS (Java KeyStore). | |||
#sonar.web.https.keystoreType=JKS | |||
# HTTPS - the name of the keystore provider to be used for the server certificate. | |||
# If not specified, the list of registered providers is traversed in preference order | |||
# and the first provider that supports the keystore type is used (see sonar.web.https.keystoreType). | |||
#sonar.web.https.keystoreProvider= | |||
# HTTPS - the pathname of the truststore file which contains trusted certificate authorities. | |||
# By default, this would be the cacerts file in your JRE. | |||
# If truststoreFile doesn't need a file use empty value. | |||
#sonar.web.https.truststoreFile= | |||
# HTTPS - the password used to access the specified truststore file. | |||
#sonar.web.https.truststorePass= | |||
# HTTPS - the type of truststore file to be used. | |||
# The default value is JKS (Java KeyStore). | |||
#sonar.web.https.truststoreType=JKS | |||
# HTTPS - the name of the truststore provider to be used for the server certificate. | |||
# If not specified, the list of registered providers is traversed in preference order | |||
# and the first provider that supports the truststore type is used (see sonar.web.https.truststoreType). | |||
#sonar.web.https.truststoreProvider= | |||
# HTTPS - whether to enable client certificate authentication. | |||
# The default is false (client certificates disabled). | |||
# Other possible values are 'want' (certificates will be requested, but not required), | |||
# and 'true' (certificates are required). | |||
#sonar.web.https.clientAuth=false | |||
# The maximum number of connections that the server will accept and process at any given time. | |||
# When this number has been reached, the server will not accept any more connections until | |||
# the number of connections falls below this value. The operating system may still accept connections | |||
# based on the sonar.web.connections.acceptCount property. The default value is 50 for each | |||
# enabled connector. | |||
#sonar.web.http.maxThreads=50 | |||
#sonar.web.https.maxThreads=50 | |||
# The minimum number of threads always kept running. The default value is 5 for each | |||
# enabled connector. | |||
#sonar.web.http.minThreads=5 | |||
#sonar.web.https.minThreads=5 | |||
# The maximum queue length for incoming connection requests when all possible request processing | |||
# threads are in use. Any requests received when the queue is full will be refused. | |||
# The default value is 25 for each enabled connector. | |||
#sonar.web.http.acceptCount=25 | |||
#sonar.web.https.acceptCount=25 | |||
# Access logs are generated in the file logs/access.log. This file is rolled over when it's 5Mb. | |||
# An archive of 3 files is kept in the same directory. | |||
# Access logs are enabled by default. | |||
#sonar.web.accessLogs.enable=true | |||
# TCP port for incoming AJP connections. Disabled when value is -1. | |||
# sonar.ajp.port=9009 | |||
#-------------------------------------------------------------------------------------------------- | |||
# UPDATE CENTER | |||
# The Update Center requires an internet connection to request http://update.sonarsource.org | |||
# It is enabled by default. | |||
#sonar.updatecenter.activate=true | |||
# HTTP proxy (default none) | |||
#http.proxyHost= | |||
#http.proxyPort= | |||
# NT domain name if NTLM proxy is used | |||
#http.auth.ntlm.domain= | |||
# SOCKS proxy (default none) | |||
#socksProxyHost= | |||
#socksProxyPort= | |||
# proxy authentication. The 2 following properties are used for HTTP and SOCKS proxies. | |||
#http.proxyUser= | |||
#http.proxyPassword= | |||
#-------------------------------------------------------------------------------------------------- | |||
# NOTIFICATIONS | |||
# Delay in seconds between processing of notification queue. Default is 60. | |||
#sonar.notifications.delay=60 | |||
#-------------------------------------------------------------------------------------------------- | |||
# PROFILING | |||
# Level of information displayed in the logs: NONE (default), BASIC (functional information) and FULL (functional and technical details) | |||
#sonar.log.profilingLevel=NONE | |||
#-------------------------------------------------------------------------------------------------- | |||
# DEVELOPMENT MODE | |||
# Only for debugging | |||
# Set to true to apply Ruby on Rails code changes on the fly | |||
#sonar.rails.dev=false |
@@ -0,0 +1,3 @@ | |||
hello: world | |||
foo=bar | |||
java.io.tmpdir=/should/be/overridden |
@@ -0,0 +1,108 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>server</artifactId> | |||
<version>4.5-SNAPSHOT</version> | |||
<relativePath>../</relativePath> | |||
</parent> | |||
<modelVersion>4.0.0</modelVersion> | |||
<artifactId>sonar-process</artifactId> | |||
<name>SonarQube :: Process</name> | |||
<dependencies> | |||
<dependency> | |||
<groupId>org.slf4j</groupId> | |||
<artifactId>slf4j-api</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>ch.qos.logback</groupId> | |||
<artifactId>logback-classic</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>commons-codec</groupId> | |||
<artifactId>commons-codec</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>commons-io</groupId> | |||
<artifactId>commons-io</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>commons-lang</groupId> | |||
<artifactId>commons-lang</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.google.code.findbugs</groupId> | |||
<artifactId>jsr305</artifactId> | |||
<scope>provided</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>junit</groupId> | |||
<artifactId>junit</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.easytesting</groupId> | |||
<artifactId>fest-assert</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.mockito</groupId> | |||
<artifactId>mockito-core</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.hamcrest</groupId> | |||
<artifactId>hamcrest-all</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.google.guava</groupId> | |||
<artifactId>guava</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.eclipse.jetty</groupId> | |||
<artifactId>jetty-server</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-jar-plugin</artifactId> | |||
<executions> | |||
<execution> | |||
<goals> | |||
<goal>test-jar</goal> | |||
</goals> | |||
</execution> | |||
</executions> | |||
</plugin> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-assembly-plugin</artifactId> | |||
<configuration> | |||
<descriptors> | |||
<descriptor>test-jar-with-dependencies.xml</descriptor> | |||
</descriptors> | |||
</configuration> | |||
<executions> | |||
<execution> | |||
<phase>package</phase> | |||
<goals> | |||
<goal>single</goal> | |||
</goals> | |||
</execution> | |||
</executions> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</project> |
@@ -0,0 +1,133 @@ | |||
/* | |||
* 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.apache.commons.codec.binary.Base64; | |||
import org.apache.commons.io.FileUtils; | |||
import org.apache.commons.lang.StringUtils; | |||
import javax.annotation.Nullable; | |||
import javax.crypto.KeyGenerator; | |||
import javax.crypto.SecretKey; | |||
import javax.crypto.spec.SecretKeySpec; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.security.Key; | |||
import java.security.SecureRandom; | |||
final class AesCipher implements Cipher { | |||
// Can't be increased because of Java 6 policy files : | |||
// https://confluence.terena.org/display/~visser/No+256+bit+ciphers+for+Java+apps | |||
// http://java.sun.com/javase/6/webnotes/install/jre/README | |||
public static final int KEY_SIZE_IN_BITS = 128; | |||
private static final String CRYPTO_KEY = "AES"; | |||
/** | |||
* Duplication from CoreProperties.ENCRYPTION_SECRET_KEY_PATH | |||
*/ | |||
static final String ENCRYPTION_SECRET_KEY_PATH = "sonar.secretKeyPath"; | |||
private String pathToSecretKey; | |||
AesCipher(@Nullable String pathToSecretKey) { | |||
this.pathToSecretKey = pathToSecretKey; | |||
} | |||
@Override | |||
public String encrypt(String clearText) { | |||
try { | |||
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_KEY); | |||
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, loadSecretFile()); | |||
return new String(Base64.encodeBase64(cipher.doFinal(clearText.getBytes("UTF-8")))); | |||
} catch (RuntimeException e) { | |||
throw e; | |||
} catch (Exception e) { | |||
throw new IllegalStateException(e); | |||
} | |||
} | |||
@Override | |||
public String decrypt(String encryptedText) { | |||
try { | |||
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_KEY); | |||
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, loadSecretFile()); | |||
byte[] cipherData = cipher.doFinal(Base64.decodeBase64(StringUtils.trim(encryptedText))); | |||
return new String(cipherData); | |||
} catch (RuntimeException e) { | |||
throw e; | |||
} catch (Exception e) { | |||
throw new IllegalStateException(e); | |||
} | |||
} | |||
/** | |||
* This method checks the existence of the file, but not the validity of the contained key. | |||
*/ | |||
boolean hasSecretKey() { | |||
String path = getPathToSecretKey(); | |||
if (StringUtils.isNotBlank(path)) { | |||
File file = new File(path); | |||
return file.exists() && file.isFile(); | |||
} | |||
return false; | |||
} | |||
private Key loadSecretFile() throws IOException { | |||
String path = getPathToSecretKey(); | |||
return loadSecretFileFromFile(path); | |||
} | |||
Key loadSecretFileFromFile(String path) throws IOException { | |||
if (StringUtils.isBlank(path)) { | |||
throw new IllegalStateException("Secret key not found. Please set the property " + ENCRYPTION_SECRET_KEY_PATH); | |||
} | |||
File file = new File(path); | |||
if (!file.exists() || !file.isFile()) { | |||
throw new IllegalStateException("The property " + ENCRYPTION_SECRET_KEY_PATH + " does not link to a valid file: " + path); | |||
} | |||
String s = FileUtils.readFileToString(file); | |||
if (StringUtils.isBlank(s)) { | |||
throw new IllegalStateException("No secret key in the file: " + path); | |||
} | |||
return new SecretKeySpec(Base64.decodeBase64(StringUtils.trim(s)), CRYPTO_KEY); | |||
} | |||
String generateRandomSecretKey() { | |||
try { | |||
KeyGenerator keyGen = KeyGenerator.getInstance(CRYPTO_KEY); | |||
keyGen.init(KEY_SIZE_IN_BITS, new SecureRandom()); | |||
SecretKey secretKey = keyGen.generateKey(); | |||
return new String(Base64.encodeBase64(secretKey.getEncoded())); | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Fail to generate secret key", e); | |||
} | |||
} | |||
String getPathToSecretKey() { | |||
if (StringUtils.isBlank(pathToSecretKey)) { | |||
pathToSecretKey = new File(FileUtils.getUserDirectoryPath(), ".sonar/sonar-secret.txt").getPath(); | |||
} | |||
return pathToSecretKey; | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
/* | |||
* 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.apache.commons.codec.binary.Base64; | |||
final class Base64Cipher implements Cipher { | |||
@Override | |||
public String encrypt(String clearText) { | |||
return new String(Base64.encodeBase64(clearText.getBytes())); | |||
} | |||
@Override | |||
public String decrypt(String encryptedText) { | |||
return new String(Base64.decodeBase64(encryptedText)); | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
/* | |||
* 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; | |||
interface Cipher { | |||
String encrypt(String clearText); | |||
String decrypt(String encryptedText); | |||
} |
@@ -0,0 +1,70 @@ | |||
/* | |||
* 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.apache.commons.io.FileUtils; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.lang.text.StrSubstitutor; | |||
import java.io.File; | |||
import java.io.FileReader; | |||
import java.util.Enumeration; | |||
import java.util.Map; | |||
import java.util.Properties; | |||
public final class ConfigurationUtils { | |||
private ConfigurationUtils() { | |||
// Utility class | |||
} | |||
public static Properties interpolateVariables(Properties properties, Map<String, String> variables) { | |||
Properties result = new Properties(); | |||
Enumeration keys = properties.keys(); | |||
while (keys.hasMoreElements()) { | |||
String key = (String) keys.nextElement(); | |||
String value = (String) properties.get(key); | |||
String interpolatedValue = StrSubstitutor.replace(value, variables, "${env:", "}"); | |||
result.setProperty(key, interpolatedValue); | |||
} | |||
return result; | |||
} | |||
static Props loadPropsFromCommandLineArgs(String[] args) { | |||
if (args.length != 1) { | |||
throw new IllegalArgumentException("Only a single command-line argument is accepted " + | |||
"(absolute path to configuration file)"); | |||
} | |||
File propertyFile = new File(args[0]); | |||
Properties properties = new Properties(); | |||
FileReader reader = null; | |||
try { | |||
reader = new FileReader(propertyFile); | |||
properties.load(reader); | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Could not read properties from file: " + args[0], e); | |||
} finally { | |||
IOUtils.closeQuietly(reader); | |||
FileUtils.deleteQuietly(propertyFile); | |||
} | |||
return new Props(properties); | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* 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.annotation.Nullable; | |||
import java.util.HashMap; | |||
import java.util.Locale; | |||
import java.util.Map; | |||
import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
/** | |||
* @since 3.0 | |||
*/ | |||
public final class Encryption { | |||
private static final String BASE64_ALGORITHM = "b64"; | |||
private static final String AES_ALGORITHM = "aes"; | |||
private final AesCipher aesCipher; | |||
private final Map<String, Cipher> ciphers = new HashMap<String, Cipher>(); | |||
private static final Pattern ENCRYPTED_PATTERN = Pattern.compile("\\{(.*?)\\}(.*)"); | |||
public Encryption(@Nullable String pathToSecretKey) { | |||
aesCipher = new AesCipher(pathToSecretKey); | |||
ciphers.put(BASE64_ALGORITHM, new Base64Cipher()); | |||
ciphers.put(AES_ALGORITHM, aesCipher); | |||
} | |||
public boolean isEncrypted(String value) { | |||
return value.indexOf('{') == 0 && value.indexOf('}') > 1; | |||
} | |||
public String decrypt(String encryptedText) { | |||
Matcher matcher = ENCRYPTED_PATTERN.matcher(encryptedText); | |||
if (matcher.matches()) { | |||
Cipher cipher = ciphers.get(matcher.group(1).toLowerCase(Locale.ENGLISH)); | |||
if (cipher != null) { | |||
return cipher.decrypt(matcher.group(2)); | |||
} | |||
} | |||
return encryptedText; | |||
} | |||
} |
@@ -0,0 +1,81 @@ | |||
/* | |||
* 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); | |||
} | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
/* | |||
* 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 class Lifecycle { | |||
private State state = State.INIT; | |||
public State getState() { | |||
return state; | |||
} | |||
public synchronized boolean tryToMoveTo(State to) { | |||
if (state.ordinal() < to.ordinal()) { | |||
state = to; | |||
return true; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public boolean equals(Object o) { | |||
if (this == o) { | |||
return true; | |||
} | |||
if (o == null || getClass() != o.getClass()) { | |||
return false; | |||
} | |||
Lifecycle lifecycle = (Lifecycle) o; | |||
return state == lifecycle.state; | |||
} | |||
@Override | |||
public int hashCode() { | |||
return state.hashCode(); | |||
} | |||
} |
@@ -0,0 +1,71 @@ | |||
/* | |||
* 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 java.net.Inet4Address; | |||
import java.net.InetAddress; | |||
import java.net.NetworkInterface; | |||
import java.net.SocketException; | |||
import java.util.Enumeration; | |||
public class LoopbackAddress { | |||
private static InetAddress instance; | |||
private LoopbackAddress() { | |||
// only static stuff | |||
} | |||
/** | |||
* Quite similar to {@code InetAddress.getLoopbackAddress()} which was introduced in Java 7. This | |||
* method aims to support Java 6. It returns an IPv4 address, but not IPv6 in order to | |||
* support {@code -Djava.net.preferIPv4Stack=true} which is recommended for Elasticsearch. | |||
*/ | |||
public static InetAddress get() { | |||
if (instance == null) { | |||
try { | |||
instance = doGet(NetworkInterface.getNetworkInterfaces()); | |||
} catch (SocketException e) { | |||
throw new IllegalStateException("Fail to browse network interfaces", e); | |||
} | |||
} | |||
return instance; | |||
} | |||
static InetAddress doGet(Enumeration<NetworkInterface> ifaces) { | |||
InetAddress result = null; | |||
while (ifaces.hasMoreElements() && result == null) { | |||
NetworkInterface iface = ifaces.nextElement(); | |||
Enumeration<InetAddress> addresses = iface.getInetAddresses(); | |||
while (addresses.hasMoreElements()) { | |||
InetAddress addr = addresses.nextElement(); | |||
if (addr.isLoopbackAddress() && addr instanceof Inet4Address) { | |||
result = addr; | |||
break; | |||
} | |||
} | |||
} | |||
if (result == null) { | |||
throw new IllegalStateException("Impossible to get a IPv4 loopback address"); | |||
} | |||
return result; | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
/* | |||
* 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 class MessageException extends RuntimeException { | |||
public MessageException(String message) { | |||
super(message); | |||
} | |||
/** | |||
* Does not fill in the stack trace | |||
* | |||
* @see Throwable#fillInStackTrace() | |||
*/ | |||
@Override | |||
public synchronized Throwable fillInStackTrace() { | |||
return this; | |||
} | |||
} |
@@ -0,0 +1,86 @@ | |||
/* | |||
* 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.apache.commons.io.FileUtils; | |||
import org.apache.commons.lang.StringUtils; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
public class MinimumViableSystem { | |||
private final Map<String, String> requiredJavaOptions = new HashMap<String, String>(); | |||
public MinimumViableSystem setRequiredJavaOption(String propertyKey, String expectedValue) { | |||
requiredJavaOptions.put(propertyKey, expectedValue); | |||
return this; | |||
} | |||
/** | |||
* Entry point for all checks | |||
*/ | |||
public void check() { | |||
checkJavaVersion(); | |||
checkJavaOptions(); | |||
checkWritableTempDir(); | |||
} | |||
/** | |||
* Verify that temp directory is writable | |||
*/ | |||
private void checkWritableTempDir() { | |||
checkWritableDir(System.getProperty("java.io.tmpdir")); | |||
} | |||
void checkWritableDir(String tempPath) { | |||
try { | |||
File tempFile = File.createTempFile("check", "tmp", new File(tempPath)); | |||
FileUtils.deleteQuietly(tempFile); | |||
} catch (IOException e) { | |||
throw new IllegalStateException(String.format("Temp directory is not writable: %s", tempPath), e); | |||
} | |||
} | |||
void checkJavaOptions() { | |||
for (Map.Entry<String, String> entry : requiredJavaOptions.entrySet()) { | |||
String value = System.getProperty(entry.getKey()); | |||
if (!StringUtils.equals(value, entry.getValue())) { | |||
throw new MessageException(String.format( | |||
"JVM option '%s' must be set to '%s'. Got '%s'", entry.getKey(), entry.getValue(), StringUtils.defaultString(value))); | |||
} | |||
} | |||
} | |||
void checkJavaVersion() { | |||
String javaVersion = System.getProperty("java.specification.version"); | |||
checkJavaVersion(javaVersion); | |||
} | |||
void checkJavaVersion(String javaVersion) { | |||
if (!javaVersion.startsWith("1.6") && !javaVersion.startsWith("1.7") && !javaVersion.startsWith("1.8")) { | |||
// still better than "java.lang.UnsupportedClassVersionError: Unsupported major.minor version 49.0 | |||
throw new MessageException(String.format("Supported versions of Java are 1.6, 1.7 and 1.8. Got %s.", javaVersion)); | |||
} | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
/* | |||
* 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 MonitoredProcess extends Terminable { | |||
/** | |||
* Starts and blocks until ready | |||
*/ | |||
void start(); | |||
void awaitTermination(); | |||
} |
@@ -0,0 +1,40 @@ | |||
/* | |||
* 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 java.net.ServerSocket; | |||
public class NetworkUtils { | |||
private NetworkUtils() { | |||
// only static stuff | |||
} | |||
public static int freePort() { | |||
try { | |||
ServerSocket s = new ServerSocket(0); | |||
int port = s.getLocalPort(); | |||
s.close(); | |||
return port; | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Can not find an open network port", e); | |||
} | |||
} | |||
} |
@@ -0,0 +1,148 @@ | |||
/* | |||
* 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.slf4j.LoggerFactory; | |||
import java.util.concurrent.Executors; | |||
import java.util.concurrent.ScheduledExecutorService; | |||
import java.util.concurrent.TimeUnit; | |||
public class ProcessEntryPoint implements ProcessMXBean { | |||
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"; | |||
private final Props props; | |||
private final Lifecycle lifecycle = new Lifecycle(); | |||
private volatile MonitoredProcess monitoredProcess; | |||
private volatile long lastPing = 0L; | |||
private volatile StopperThread stopperThread; | |||
private final SystemExit exit; | |||
private Thread shutdownHook = new Thread(new Runnable() { | |||
@Override | |||
public void run() { | |||
exit.setInShutdownHook(); | |||
terminate(); | |||
} | |||
}); | |||
ProcessEntryPoint(Props props, SystemExit exit) { | |||
this.props = props; | |||
this.exit = exit; | |||
} | |||
public Props getProps() { | |||
return props; | |||
} | |||
/** | |||
* Launch process and waits until it's down | |||
*/ | |||
public void launch(MonitoredProcess mp) { | |||
if (!lifecycle.tryToMoveTo(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(); | |||
} | |||
try { | |||
monitoredProcess.start(); | |||
if (lifecycle.tryToMoveTo(State.STARTED)) { | |||
monitoredProcess.awaitTermination(); | |||
} | |||
} catch (Exception ignored) { | |||
} finally { | |||
terminate(); | |||
} | |||
} | |||
@Override | |||
public boolean isReady() { | |||
return lifecycle.getState() == State.STARTED; | |||
} | |||
@Override | |||
public void ping() { | |||
lastPing = System.currentTimeMillis(); | |||
} | |||
/** | |||
* 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))); | |||
stopperThread.start(); | |||
} | |||
try { | |||
// stopperThread is not null for sure | |||
// join() does nothing if thread already finished | |||
stopperThread.join(); | |||
lifecycle.tryToMoveTo(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() { | |||
return lifecycle.getState(); | |||
} | |||
Thread getShutdownHook() { | |||
return shutdownHook; | |||
} | |||
public static ProcessEntryPoint createForArguments(String[] args) { | |||
Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args); | |||
return new ProcessEntryPoint(props, new SystemExit()); | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
/* | |||
* 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 ch.qos.logback.classic.LoggerContext; | |||
import ch.qos.logback.classic.joran.JoranConfigurator; | |||
import ch.qos.logback.core.joran.spi.JoranException; | |||
import ch.qos.logback.core.util.StatusPrinter; | |||
import org.slf4j.LoggerFactory; | |||
public class ProcessLogging { | |||
private static final String PATH_LOGS_PROPERTY = "sonar.path.logs"; | |||
public void configure(Props props, String logbackXmlResource) { | |||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); | |||
try { | |||
JoranConfigurator configurator = new JoranConfigurator(); | |||
configurator.setContext(context); | |||
context.reset(); | |||
context.putProperty(PATH_LOGS_PROPERTY, props.nonNullValue(PATH_LOGS_PROPERTY)); | |||
doConfigure(configurator, logbackXmlResource); | |||
} catch (JoranException ignored) { | |||
// StatusPrinter will handle this | |||
} | |||
StatusPrinter.printInCaseOfErrorsOrWarnings(context); | |||
} | |||
/** | |||
* Extracted only for unit testing | |||
*/ | |||
void doConfigure(JoranConfigurator configurator, String logbackXmlResource) throws JoranException { | |||
configurator.doConfigure(getClass().getResource(logbackXmlResource)); | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
/* | |||
* 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(); | |||
} |
@@ -0,0 +1,77 @@ | |||
/* | |||
* 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.apache.commons.io.IOUtils; | |||
import org.slf4j.LoggerFactory; | |||
import javax.annotation.Nullable; | |||
public class ProcessUtils { | |||
private ProcessUtils() { | |||
// only static stuff | |||
} | |||
/** | |||
* Do not abuse to this method. It uses exceptions to get status. | |||
* @return false if process is null or terminated, else true. | |||
*/ | |||
public static boolean isAlive(@Nullable Process process) { | |||
boolean alive = false; | |||
if (process != null) { | |||
try { | |||
process.exitValue(); | |||
} catch (IllegalThreadStateException ignored) { | |||
alive = true; | |||
} | |||
} | |||
return alive; | |||
} | |||
/** | |||
* Destroys process (equivalent to kill -9) if alive | |||
* @return true if the process was destroyed, false if process is null or already destroyed. | |||
*/ | |||
public static boolean destroyQuietly(@Nullable Process process) { | |||
boolean destroyed = 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; | |||
} catch (Exception e) { | |||
LoggerFactory.getLogger(ProcessUtils.class).error("Fail to destroy " + process); | |||
} | |||
} | |||
return destroyed; | |||
} | |||
public static void closeStreams(@Nullable Process process) { | |||
if (process != null) { | |||
IOUtils.closeQuietly(process.getInputStream()); | |||
IOUtils.closeQuietly(process.getOutputStream()); | |||
IOUtils.closeQuietly(process.getErrorStream()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,120 @@ | |||
/* | |||
* 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.apache.commons.lang.StringUtils; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import java.io.File; | |||
import java.util.Properties; | |||
public class Props { | |||
private final Properties properties; | |||
private final Encryption encryption; | |||
public Props(Properties props) { | |||
this.properties = props; | |||
this.encryption = new Encryption(props.getProperty(AesCipher.ENCRYPTION_SECRET_KEY_PATH)); | |||
} | |||
public boolean contains(String key) { | |||
return properties.containsKey(key); | |||
} | |||
@CheckForNull | |||
public String value(String key) { | |||
String value = properties.getProperty(key); | |||
if (value != null && encryption.isEncrypted(value)) { | |||
value = encryption.decrypt(value); | |||
} | |||
return value; | |||
} | |||
public String nonNullValue(String key) { | |||
String value = value(key); | |||
if (value == null) { | |||
throw new IllegalArgumentException("Missing property: " + key); | |||
} | |||
return value; | |||
} | |||
@CheckForNull | |||
public String value(String key, @Nullable String defaultValue) { | |||
String s = value(key); | |||
return s == null ? defaultValue : s; | |||
} | |||
public boolean valueAsBoolean(String key) { | |||
String s = value(key); | |||
return s != null && Boolean.parseBoolean(s); | |||
} | |||
public boolean valueAsBoolean(String key, boolean defaultValue) { | |||
String s = value(key); | |||
return s != null ? Boolean.parseBoolean(s) : defaultValue; | |||
} | |||
public File nonNullValueAsFile(String key) { | |||
String s = value(key); | |||
if (s == null) { | |||
throw new IllegalArgumentException("Property " + key + " is missing"); | |||
} | |||
return new File(s); | |||
} | |||
@CheckForNull | |||
public Integer valueAsInt(String key) { | |||
String s = value(key); | |||
if (s != null && !"".equals(s)) { | |||
try { | |||
return Integer.parseInt(s); | |||
} catch (NumberFormatException e) { | |||
throw new IllegalStateException("Value of property " + key + " is not an integer: " + s, e); | |||
} | |||
} | |||
return null; | |||
} | |||
public int valueAsInt(String key, int defaultValue) { | |||
Integer i = valueAsInt(key); | |||
return i == null ? defaultValue : i; | |||
} | |||
public Properties rawProperties() { | |||
return properties; | |||
} | |||
public Props set(String key, @Nullable String value) { | |||
if (value != null) { | |||
properties.setProperty(key, value); | |||
} | |||
return this; | |||
} | |||
public void setDefault(String key, String value) { | |||
String s = properties.getProperty(key); | |||
if (StringUtils.isBlank(s)) { | |||
properties.setProperty(key, value); | |||
} | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
/* | |||
* 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 | |||
} |
@@ -0,0 +1,57 @@ | |||
/* | |||
* 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 java.util.concurrent.ExecutorService; | |||
import java.util.concurrent.Executors; | |||
import java.util.concurrent.Future; | |||
import java.util.concurrent.TimeUnit; | |||
/** | |||
* Gracefully stops process, but exits JVM if too long | |||
*/ | |||
class StopperThread extends Thread { | |||
private final Terminable terminable; | |||
private final long terminationTimeout; | |||
StopperThread(Terminable terminable, long terminationTimeout) { | |||
super("Stopper"); | |||
this.terminable = terminable; | |||
this.terminationTimeout = terminationTimeout; | |||
} | |||
@Override | |||
public void run() { | |||
ExecutorService executor = Executors.newSingleThreadExecutor(); | |||
Future future = executor.submit(new Runnable() { | |||
@Override | |||
public void run() { | |||
terminable.terminate(); | |||
} | |||
}); | |||
try { | |||
future.get(terminationTimeout, TimeUnit.MILLISECONDS); | |||
} catch (Exception e) { | |||
future.cancel(true); | |||
executor.shutdownNow(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
/* | |||
* 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 java.util.concurrent.atomic.AtomicBoolean; | |||
/** | |||
* Calls {@link System#exit(int)} except from shutdown hooks, to prevent | |||
* deadlocks. See http://stackoverflow.com/a/19552359/229031 | |||
*/ | |||
public class SystemExit { | |||
private final AtomicBoolean inShutdownHook = new AtomicBoolean(false); | |||
public void exit(int code) { | |||
if (!inShutdownHook.get()) { | |||
doExit(code); | |||
} | |||
} | |||
public boolean isInShutdownHook() { | |||
return inShutdownHook.get(); | |||
} | |||
/** | |||
* Declarative approach. I don't know how to get this lifecycle state from Java API. | |||
*/ | |||
public void setInShutdownHook() { | |||
inShutdownHook.set(true); | |||
} | |||
void doExit(int code) { | |||
System.exit(code); | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
/* | |||
* 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,0 +1,23 @@ | |||
/* | |||
* 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. | |||
*/ | |||
@ParametersAreNonnullByDefault | |||
package org.sonar.process; | |||
import javax.annotation.ParametersAreNonnullByDefault; |
@@ -0,0 +1,185 @@ | |||
/* | |||
* 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 com.google.common.io.Resources; | |||
import org.apache.commons.codec.binary.Base64; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import javax.crypto.BadPaddingException; | |||
import java.io.File; | |||
import java.security.InvalidKeyException; | |||
import java.security.Key; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.fest.assertions.Fail.fail; | |||
public class AesCipherTest { | |||
@Rule | |||
public ExpectedException thrown = ExpectedException.none(); | |||
@Test | |||
public void generateRandomSecretKey() { | |||
AesCipher cipher = new AesCipher(null); | |||
String key = cipher.generateRandomSecretKey(); | |||
assertThat(StringUtils.isNotBlank(key)).isTrue(); | |||
assertThat(Base64.isArrayByteBase64(key.getBytes())).isTrue(); | |||
} | |||
@Test | |||
public void encrypt() throws Exception { | |||
AesCipher cipher = new AesCipher(pathToSecretKey()); | |||
String encryptedText = cipher.encrypt("this is a secret"); | |||
assertThat(StringUtils.isNotBlank(encryptedText)).isTrue(); | |||
assertThat(Base64.isArrayByteBase64(encryptedText.getBytes())).isTrue(); | |||
} | |||
@Test | |||
public void encrypt_bad_key() throws Exception { | |||
thrown.expect(RuntimeException.class); | |||
thrown.expectMessage("Invalid AES key"); | |||
AesCipher cipher = new AesCipher(getPath("bad_secret_key.txt")); | |||
cipher.encrypt("this is a secret"); | |||
} | |||
@Test | |||
public void decrypt() throws Exception { | |||
AesCipher cipher = new AesCipher(pathToSecretKey()); | |||
// the following value has been encrypted with the key /org/sonar/api/config/AesCipherTest/aes_secret_key.txt | |||
String clearText = cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY="); | |||
assertThat(clearText).isEqualTo("this is a secret"); | |||
} | |||
@Test | |||
public void decrypt_bad_key() throws Exception { | |||
AesCipher cipher = new AesCipher(getPath("bad_secret_key.txt")); | |||
try { | |||
cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY="); | |||
fail(); | |||
} catch (RuntimeException e) { | |||
assertThat(e.getCause()).isInstanceOf(InvalidKeyException.class); | |||
} | |||
} | |||
@Test | |||
public void decrypt_other_key() throws Exception { | |||
AesCipher cipher = new AesCipher(getPath("other_secret_key.txt")); | |||
try { | |||
// text encrypted with another key | |||
cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY="); | |||
fail(); | |||
} catch (RuntimeException e) { | |||
assertThat(e.getCause()).isInstanceOf(BadPaddingException.class); | |||
} | |||
} | |||
@Test | |||
public void encryptThenDecrypt() throws Exception { | |||
AesCipher cipher = new AesCipher(pathToSecretKey()); | |||
assertThat(cipher.decrypt(cipher.encrypt("foo"))).isEqualTo("foo"); | |||
} | |||
@Test | |||
public void testDefaultPathToSecretKey() { | |||
AesCipher cipher = new AesCipher(null); | |||
String path = cipher.getPathToSecretKey(); | |||
assertThat(StringUtils.isNotBlank(path)).isTrue(); | |||
assertThat(new File(path).getName()).isEqualTo("sonar-secret.txt"); | |||
} | |||
@Test | |||
public void loadSecretKeyFromFile() throws Exception { | |||
AesCipher cipher = new AesCipher(null); | |||
Key secretKey = cipher.loadSecretFileFromFile(pathToSecretKey()); | |||
assertThat(secretKey.getAlgorithm()).isEqualTo("AES"); | |||
assertThat(secretKey.getEncoded().length).isGreaterThan(10); | |||
} | |||
@Test | |||
public void loadSecretKeyFromFile_trim_content() throws Exception { | |||
String path = getPath("non_trimmed_secret_key.txt"); | |||
AesCipher cipher = new AesCipher(null); | |||
Key secretKey = cipher.loadSecretFileFromFile(path); | |||
assertThat(secretKey.getAlgorithm()).isEqualTo("AES"); | |||
assertThat(secretKey.getEncoded().length).isGreaterThan(10); | |||
} | |||
@Test | |||
public void loadSecretKeyFromFile_file_does_not_exist() throws Exception { | |||
thrown.expect(IllegalStateException.class); | |||
AesCipher cipher = new AesCipher(null); | |||
cipher.loadSecretFileFromFile("/file/does/not/exist"); | |||
} | |||
@Test | |||
public void loadSecretKeyFromFile_no_property() throws Exception { | |||
thrown.expect(IllegalStateException.class); | |||
AesCipher cipher = new AesCipher(null); | |||
cipher.loadSecretFileFromFile(null); | |||
} | |||
@Test | |||
public void hasSecretKey() throws Exception { | |||
AesCipher cipher = new AesCipher(pathToSecretKey()); | |||
assertThat(cipher.hasSecretKey()).isTrue(); | |||
} | |||
@Test | |||
public void doesNotHaveSecretKey() throws Exception { | |||
AesCipher cipher = new AesCipher("/my/twitter/id/is/SimonBrandhof"); | |||
assertThat(cipher.hasSecretKey()).isFalse(); | |||
} | |||
private static String getPath(String file) { | |||
return Resources.getResource(AesCipherTest.class, "AesCipherTest/" + file).getPath(); | |||
} | |||
private static String pathToSecretKey() throws Exception { | |||
return getPath("aes_secret_key.txt"); | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
* 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.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 { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
public static final String DUMMY_OK_APP = "org.sonar.application.DummyOkProcess"; | |||
int freePort; | |||
File dummyAppJar; | |||
Process proc; | |||
@Before | |||
public void setup() throws IOException { | |||
ServerSocket socket = new ServerSocket(0); | |||
freePort = socket.getLocalPort(); | |||
socket.close(); | |||
dummyAppJar = FileUtils.toFile(getClass().getResource("/sonar-dummy-app.jar")); | |||
} | |||
@After | |||
public void tearDown() { | |||
if (proc != null) { | |||
ProcessUtils.destroyQuietly(proc); | |||
} | |||
} | |||
} |
@@ -0,0 +1,95 @@ | |||
/* | |||
* 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 com.google.common.collect.Maps; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import java.io.File; | |||
import java.util.Map; | |||
import java.util.Properties; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.fest.assertions.Fail.fail; | |||
public class ConfigurationUtilsTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@Test | |||
public void shouldInterpolateVariables() { | |||
Properties input = new Properties(); | |||
input.setProperty("hello", "world"); | |||
input.setProperty("url", "${env:SONAR_JDBC_URL}"); | |||
input.setProperty("do_not_change", "${SONAR_JDBC_URL}"); | |||
Map<String, String> variables = Maps.newHashMap(); | |||
variables.put("SONAR_JDBC_URL", "jdbc:h2:mem"); | |||
Properties output = ConfigurationUtils.interpolateVariables(input, variables); | |||
assertThat(output).hasSize(3); | |||
assertThat(output.getProperty("hello")).isEqualTo("world"); | |||
assertThat(output.getProperty("url")).isEqualTo("jdbc:h2:mem"); | |||
assertThat(output.getProperty("do_not_change")).isEqualTo("${SONAR_JDBC_URL}"); | |||
// input is not changed | |||
assertThat(input).hasSize(3); | |||
assertThat(input.getProperty("hello")).isEqualTo("world"); | |||
assertThat(input.getProperty("url")).isEqualTo("${env:SONAR_JDBC_URL}"); | |||
assertThat(input.getProperty("do_not_change")).isEqualTo("${SONAR_JDBC_URL}"); | |||
} | |||
@Test | |||
public void loadPropsFromCommandLineArgs_missing_argument() throws Exception { | |||
try { | |||
ConfigurationUtils.loadPropsFromCommandLineArgs(new String[0]); | |||
fail(); | |||
} catch (IllegalArgumentException e) { | |||
assertThat(e.getMessage()).startsWith("Only a single command-line argument is accepted"); | |||
} | |||
} | |||
@Test | |||
public void loadPropsFromCommandLineArgs_load_properties_from_file() throws Exception { | |||
File propsFile = temp.newFile(); | |||
FileUtils.write(propsFile, "foo=bar"); | |||
Props result = ConfigurationUtils.loadPropsFromCommandLineArgs(new String[] {propsFile.getAbsolutePath()}); | |||
assertThat(result.value("foo")).isEqualTo("bar"); | |||
assertThat(result.rawProperties()).hasSize(1); | |||
} | |||
@Test | |||
public void loadPropsFromCommandLineArgs_file_does_not_exist() throws Exception { | |||
File propsFile = temp.newFile(); | |||
FileUtils.deleteQuietly(propsFile); | |||
try { | |||
ConfigurationUtils.loadPropsFromCommandLineArgs(new String[]{propsFile.getAbsolutePath()}); | |||
fail(); | |||
} catch (IllegalStateException e) { | |||
assertThat(e).hasMessage("Could not read properties from file: " + propsFile.getAbsolutePath()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
* 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.hamcrest.Matchers.is; | |||
import static org.junit.Assert.assertThat; | |||
public class EncryptionTest { | |||
@Test | |||
public void isEncrypted() { | |||
Encryption encryption = new Encryption(null); | |||
assertThat(encryption.isEncrypted("{aes}ADASDASAD"), is(true)); | |||
assertThat(encryption.isEncrypted("{b64}ADASDASAD"), is(true)); | |||
assertThat(encryption.isEncrypted("{abc}ADASDASAD"), is(true)); | |||
assertThat(encryption.isEncrypted("{}"), is(false)); | |||
assertThat(encryption.isEncrypted("{foo"), is(false)); | |||
assertThat(encryption.isEncrypted("foo{aes}"), is(false)); | |||
} | |||
@Test | |||
public void decrypt() { | |||
Encryption encryption = new Encryption(null); | |||
assertThat(encryption.decrypt("{b64}Zm9v"), is("foo")); | |||
} | |||
@Test | |||
public void decrypt_unknown_algorithm() { | |||
Encryption encryption = new Encryption(null); | |||
assertThat(encryption.decrypt("{xxx}Zm9v"), is("{xxx}Zm9v")); | |||
} | |||
@Test | |||
public void decrypt_uncrypted_text() { | |||
Encryption encryption = new Encryption(null); | |||
assertThat(encryption.decrypt("foo"), is("foo")); | |||
} | |||
} |
@@ -0,0 +1,120 @@ | |||
/* | |||
* 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 testRegisterMBean() 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(); | |||
} | |||
@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"); | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
/* | |||
* 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 LifecycleTest { | |||
@Test | |||
public void equals_and_hashcode() throws Exception { | |||
Lifecycle init = new Lifecycle(); | |||
assertThat(init.equals(init)).isTrue(); | |||
assertThat(init.equals(new Lifecycle())).isTrue(); | |||
assertThat(init.equals("INIT")).isFalse(); | |||
assertThat(init.equals(null)).isFalse(); | |||
assertThat(init.hashCode()).isEqualTo(new Lifecycle().hashCode()); | |||
// different state | |||
Lifecycle stopping = new Lifecycle(); | |||
stopping.tryToMoveTo(State.STOPPING); | |||
assertThat(stopping).isNotEqualTo(init); | |||
} | |||
} |
@@ -0,0 +1,51 @@ | |||
/* | |||
* 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 com.google.common.collect.Iterators; | |||
import org.junit.Test; | |||
import java.net.NetworkInterface; | |||
import java.util.Collections; | |||
import java.util.Enumeration; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.fest.assertions.Fail.fail; | |||
public class LoopbackAddressTest { | |||
@Test | |||
public void get() throws Exception { | |||
assertThat(LoopbackAddress.get()).isNotNull(); | |||
assertThat(LoopbackAddress.get().isLoopbackAddress()).isTrue(); | |||
assertThat(LoopbackAddress.get().getHostAddress()).isNotNull(); | |||
} | |||
@Test | |||
public void fail_to_get_loopback_address() throws Exception { | |||
Enumeration<NetworkInterface> ifaces = Iterators.asEnumeration(Collections.<NetworkInterface>emptyList().iterator()); | |||
try { | |||
LoopbackAddress.doGet(ifaces); | |||
fail(); | |||
} catch (IllegalStateException e) { | |||
assertThat(e).hasMessage("Impossible to get a IPv4 loopback address"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,102 @@ | |||
/* | |||
* 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.fest.assertions.Assertions; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import java.io.File; | |||
import static org.fest.assertions.Fail.fail; | |||
public class MinimumViableSystemTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
/** | |||
* Verifies that all checks can be verified without error. | |||
* Test environment does not necessarily follows all checks. | |||
*/ | |||
@Test | |||
public void check() throws Exception { | |||
MinimumViableSystem mve = new MinimumViableSystem(); | |||
try { | |||
mve.check(); | |||
// ok | |||
} catch (MessageException e) { | |||
// also ok. All other exceptions are errors. | |||
} | |||
} | |||
@Test | |||
public void checkJavaVersion() throws Exception { | |||
MinimumViableSystem mve = new MinimumViableSystem(); | |||
// yes, sources are compiled with a supported Java version! | |||
mve.checkJavaVersion(); | |||
mve.checkJavaVersion("1.6"); | |||
try { | |||
mve.checkJavaVersion("1.9"); | |||
fail(); | |||
} catch (MessageException e) { | |||
Assertions.assertThat(e).hasMessage("Supported versions of Java are 1.6, 1.7 and 1.8. Got 1.9."); | |||
} | |||
} | |||
@Test | |||
public void checkJavaOption() throws Exception { | |||
String key = "MinimumViableEnvironmentTest.test.prop"; | |||
MinimumViableSystem mve = new MinimumViableSystem() | |||
.setRequiredJavaOption(key, "true"); | |||
try { | |||
System.setProperty(key, "false"); | |||
mve.checkJavaOptions(); | |||
fail(); | |||
} catch (MessageException e) { | |||
Assertions.assertThat(e).hasMessage("JVM option '" + key + "' must be set to 'true'. Got 'false'"); | |||
} | |||
System.setProperty(key, "true"); | |||
mve.checkJavaOptions(); | |||
// do not fail | |||
} | |||
@Test | |||
public void checkWritableTempDir() throws Exception { | |||
File dir = temp.newFolder(); | |||
MinimumViableSystem mve = new MinimumViableSystem(); | |||
mve.checkWritableDir(dir.getAbsolutePath()); | |||
dir.delete(); | |||
try { | |||
mve.checkWritableDir(dir.getAbsolutePath()); | |||
fail(); | |||
} catch (IllegalStateException e) { | |||
Assertions.assertThat(e).hasMessage("Temp directory is not writable: " + dir.getAbsolutePath()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,61 @@ | |||
/* | |||
* 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 java.net.ServerSocket; | |||
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); | |||
} | |||
@Test | |||
public void find_multiple_free_port() throws Exception { | |||
int port1 = NetworkUtils.freePort(); | |||
int port2 = NetworkUtils.freePort(); | |||
assertThat(port1).isGreaterThan(1024); | |||
assertThat(port2).isGreaterThan(1024); | |||
assertThat(port1).isNotSameAs(port2); | |||
} | |||
@Test | |||
public void find_multiple_free_non_adjacent_port() throws Exception { | |||
int port1 = NetworkUtils.freePort(); | |||
ServerSocket socket = new ServerSocket(port1 + 1); | |||
int port2 = NetworkUtils.freePort(); | |||
assertThat(port1).isGreaterThan(1024); | |||
assertThat(port2).isGreaterThan(1024); | |||
assertThat(port1).isNotSameAs(port2); | |||
} | |||
} |
@@ -0,0 +1,224 @@ | |||
/* | |||
* 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.apache.commons.io.FileUtils; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.junit.rules.Timeout; | |||
import org.sonar.process.test.StandardProcess; | |||
import java.io.File; | |||
import java.util.Properties; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.fest.assertions.Fail.fail; | |||
import static org.mockito.Mockito.mock; | |||
public class ProcessEntryPointTest { | |||
SystemExit exit = mock(SystemExit.class); | |||
/** | |||
* Safeguard | |||
*/ | |||
@Rule | |||
public Timeout timeout = new Timeout(10000); | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@Test | |||
public void load_properties_from_file() throws Exception { | |||
File propsFile = temp.newFile(); | |||
FileUtils.write(propsFile, "sonar.foo=bar"); | |||
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(new String[]{propsFile.getAbsolutePath()}); | |||
assertThat(entryPoint.getProps().value("sonar.foo")).isEqualTo("bar"); | |||
} | |||
@Test | |||
public void test_initial_state() throws Exception { | |||
Props props = new Props(new Properties()); | |||
ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); | |||
assertThat(entryPoint.getProps()).isSameAs(props); | |||
assertThat(entryPoint.isReady()).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); | |||
entryPoint.launch(new NoopProcess()); | |||
try { | |||
entryPoint.launch(new NoopProcess()); | |||
fail(); | |||
} catch (IllegalStateException e) { | |||
assertThat(e).hasMessage("Already started"); | |||
} | |||
} | |||
@Test | |||
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 StandardProcess process = new StandardProcess(); | |||
Thread runner = new Thread() { | |||
@Override | |||
public void run() { | |||
// starts and waits until terminated | |||
entryPoint.launch(process); | |||
} | |||
}; | |||
runner.start(); | |||
while (process.getState() != State.STARTED) { | |||
Thread.sleep(10L); | |||
} | |||
// requests for termination -> waits until down | |||
// Should terminate before the timeout of 30s | |||
entryPoint.terminate(); | |||
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 StandardProcess process = new StandardProcess(); | |||
Thread runner = new Thread() { | |||
@Override | |||
public void run() { | |||
// starts and waits until terminated | |||
entryPoint.launch(process); | |||
} | |||
}; | |||
runner.start(); | |||
while (process.getState() != State.STARTED) { | |||
Thread.sleep(10L); | |||
} | |||
// emulate signal to shutdown process | |||
entryPoint.getShutdownHook().start(); | |||
while (process.getState() != State.STOPPED) { | |||
Thread.sleep(10L); | |||
} | |||
// exit before test timeout, ok ! | |||
} | |||
@Test | |||
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(); | |||
entryPoint.launch(process); | |||
assertThat(entryPoint.getState()).isEqualTo(State.STOPPED); | |||
} | |||
private static class NoopProcess implements MonitoredProcess { | |||
@Override | |||
public void start() { | |||
} | |||
@Override | |||
public void awaitTermination() { | |||
} | |||
@Override | |||
public void terminate() { | |||
} | |||
} | |||
private static class StartupErrorProcess implements MonitoredProcess { | |||
@Override | |||
public void start() { | |||
throw new IllegalStateException("ERROR"); | |||
} | |||
@Override | |||
public void awaitTermination() { | |||
} | |||
@Override | |||
public void terminate() { | |||
} | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
/* | |||
* 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 { | |||
} |
@@ -0,0 +1,135 @@ | |||
/* | |||
* 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 java.util.Properties; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.fest.assertions.Fail.fail; | |||
public class PropsTest { | |||
@Test | |||
public void of() throws Exception { | |||
Properties p = new Properties(); | |||
p.setProperty("foo", "bar"); | |||
Props props = new Props(p); | |||
assertThat(props.value("foo")).isEqualTo("bar"); | |||
assertThat(props.value("foo", "default value")).isEqualTo("bar"); | |||
assertThat(props.value("unknown")).isNull(); | |||
assertThat(props.value("unknown", "default value")).isEqualTo("default value"); | |||
} | |||
@Test | |||
public void intOf() throws Exception { | |||
Properties p = new Properties(); | |||
p.setProperty("foo", "33"); | |||
p.setProperty("blank", ""); | |||
Props props = new Props(p); | |||
assertThat(props.valueAsInt("foo")).isEqualTo(33); | |||
assertThat(props.valueAsInt("foo", 44)).isEqualTo(33); | |||
assertThat(props.valueAsInt("blank")).isNull(); | |||
assertThat(props.valueAsInt("blank", 55)).isEqualTo(55); | |||
assertThat(props.valueAsInt("unknown")).isNull(); | |||
assertThat(props.valueAsInt("unknown", 44)).isEqualTo(44); | |||
} | |||
@Test | |||
public void intOf_not_integer() throws Exception { | |||
Properties p = new Properties(); | |||
p.setProperty("foo", "bar"); | |||
Props props = new Props(p); | |||
try { | |||
props.valueAsInt("foo"); | |||
fail(); | |||
} catch (IllegalStateException e) { | |||
assertThat(e).hasMessage("Value of property foo is not an integer: bar"); | |||
} | |||
} | |||
@Test | |||
public void booleanOf() throws Exception { | |||
Properties p = new Properties(); | |||
p.setProperty("foo", "True"); | |||
p.setProperty("bar", "false"); | |||
Props props = new Props(p); | |||
assertThat(props.valueAsBoolean("foo")).isTrue(); | |||
assertThat(props.valueAsBoolean("bar")).isFalse(); | |||
assertThat(props.valueAsBoolean("unknown")).isFalse(); | |||
} | |||
@Test | |||
public void booleanOf_default_value() throws Exception { | |||
Properties p = new Properties(); | |||
p.setProperty("foo", "true"); | |||
p.setProperty("bar", "false"); | |||
Props props = new Props(p); | |||
assertThat(props.valueAsBoolean("unset", false)).isFalse(); | |||
assertThat(props.valueAsBoolean("unset", true)).isTrue(); | |||
assertThat(props.valueAsBoolean("foo", false)).isTrue(); | |||
assertThat(props.valueAsBoolean("bar", true)).isFalse(); | |||
} | |||
@Test | |||
public void setDefault() throws Exception { | |||
Properties p = new Properties(); | |||
p.setProperty("foo", "foo_value"); | |||
Props props = new Props(p); | |||
props.setDefault("foo", "foo_def"); | |||
props.setDefault("bar", "bar_def"); | |||
assertThat(props.value("foo")).isEqualTo("foo_value"); | |||
assertThat(props.value("bar")).isEqualTo("bar_def"); | |||
assertThat(props.value("other")).isNull(); | |||
} | |||
@Test | |||
public void set() throws Exception { | |||
Properties p = new Properties(); | |||
p.setProperty("foo", "old_foo"); | |||
Props props = new Props(p); | |||
props.set("foo", "new_foo"); | |||
props.set("bar", "new_bar"); | |||
assertThat(props.value("foo")).isEqualTo("new_foo"); | |||
assertThat(props.value("bar")).isEqualTo("new_bar"); | |||
} | |||
@Test | |||
public void raw_properties() throws Exception { | |||
Properties p = new Properties(); | |||
p.setProperty("encrypted_prop", "{aes}abcde"); | |||
p.setProperty("clear_prop", "foo"); | |||
Props props = new Props(p); | |||
assertThat(props.rawProperties()).hasSize(2); | |||
// do not decrypt | |||
assertThat(props.rawProperties().get("encrypted_prop")).isEqualTo("{aes}abcde"); | |||
assertThat(props.rawProperties().get("clear_prop")).isEqualTo("foo"); | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
/* | |||
* 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 java.util.concurrent.atomic.AtomicInteger; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
public class SystemExitTest { | |||
@Test | |||
public void do_not_exit_if_in_shutdown_hook() throws Exception { | |||
SystemExit systemExit = new SystemExit(); | |||
systemExit.setInShutdownHook(); | |||
assertThat(systemExit.isInShutdownHook()).isTrue(); | |||
systemExit.exit(0); | |||
// still there | |||
} | |||
@Test | |||
public void exit_if_not_in_shutdown_hook() throws Exception { | |||
final AtomicInteger got = new AtomicInteger(); | |||
SystemExit systemExit = new SystemExit() { | |||
@Override | |||
void doExit(int code) { | |||
got.set(code); | |||
} | |||
}; | |||
assertThat(systemExit.isInShutdownHook()).isFalse(); | |||
systemExit.exit(1); | |||
assertThat(got.get()).isEqualTo(1); | |||
} | |||
} |
@@ -0,0 +1,116 @@ | |||
/* | |||
* 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.apache.commons.io.FileUtils; | |||
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.ProcessEntryPoint; | |||
import javax.servlet.ServletException; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.File; | |||
import java.io.IOException; | |||
/** | |||
* Http server used for testing (see MonitorTest). It accepts HTTP commands /ping and /kill to hardly exit. | |||
* 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 { | |||
private final Server server; | |||
// temp dir is specific to this process | |||
private final File tempDir = new File(System.getProperty("java.io.tmpdir")); | |||
public HttpProcess(int httpPort) { | |||
server = new Server(httpPort); | |||
} | |||
@Override | |||
public void start() { | |||
writeTimeToFile("startingAt"); | |||
ContextHandler context = new ContextHandler(); | |||
context.setContextPath("/"); | |||
context.setClassLoader(Thread.currentThread().getContextClassLoader()); | |||
server.setHandler(context); | |||
context.setHandler(new AbstractHandler() { | |||
@Override | |||
public void handle(String target, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { | |||
if ("/ping".equals(target)) { | |||
request.setHandled(true); | |||
httpServletResponse.getWriter().print("ping"); | |||
} else if ("/kill".equals(target)) { | |||
writeTimeToFile("killedAt"); | |||
System.exit(0); | |||
} | |||
} | |||
}); | |||
try { | |||
server.start(); | |||
while (!server.isStarted()) { | |||
Thread.sleep(100L); | |||
} | |||
writeTimeToFile("readyAt"); | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Fail to start Jetty", e); | |||
} | |||
} | |||
@Override | |||
public void awaitTermination() { | |||
try { | |||
server.join(); | |||
} catch (InterruptedException ignore) { | |||
} | |||
} | |||
@Override | |||
public void terminate() { | |||
try { | |||
if (!server.isStopped()) { | |||
server.stop(); | |||
writeTimeToFile("terminatedAt"); | |||
} | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Fail to stop Jetty", e); | |||
} | |||
} | |||
private void writeTimeToFile(String filename) { | |||
try { | |||
FileUtils.write(new File(tempDir, filename), String.valueOf(System.currentTimeMillis())); | |||
} catch (IOException e) { | |||
throw new IllegalStateException(e); | |||
} | |||
} | |||
public static void main(String[] args) { | |||
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); | |||
entryPoint.launch(new HttpProcess(entryPoint.getProps().valueAsInt("httpPort"))); | |||
} | |||
} |
@@ -0,0 +1,81 @@ | |||
/* | |||
* 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.MonitoredProcess; | |||
import org.sonar.process.ProcessEntryPoint; | |||
import org.sonar.process.State; | |||
public class StandardProcess implements MonitoredProcess { | |||
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 void awaitTermination() { | |||
try { | |||
daemon.join(); | |||
} catch (InterruptedException e) { | |||
// interrupted by call to terminate() | |||
} | |||
} | |||
/** | |||
* Blocks until stopped | |||
*/ | |||
@Override | |||
public void terminate() { | |||
state = State.STOPPING; | |||
daemon.interrupt(); | |||
state = State.STOPPED; | |||
} | |||
public State getState() { | |||
return state; | |||
} | |||
public static void main(String[] args) { | |||
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); | |||
entryPoint.launch(new StandardProcess()); | |||
System.exit(0); | |||
} | |||
} |
@@ -0,0 +1 @@ | |||
0PZz+G+f8mjr3sPn4+AhHg== |
@@ -0,0 +1 @@ | |||
badbadbad== |
@@ -0,0 +1,3 @@ | |||
0PZz+G+f8mjr3sPn4+AhHg== | |||
@@ -0,0 +1 @@ | |||
IBxEUxZ41c8XTxyaah1Qlg== |
@@ -0,0 +1 @@ | |||
<configuration/> |
@@ -0,0 +1,212 @@ | |||
# This file must contain only ISO 8859-1 characters | |||
# see http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Properties.html#load(java.io.InputStream) | |||
# | |||
# To use an environment variable, use the following syntax : ${env:NAME_OF_ENV_VARIABLE} | |||
# For example: | |||
# sonar.jdbc.url= ${env:SONAR_JDBC_URL} | |||
# | |||
# | |||
# See also the file conf/wrapper.conf for JVM advanced settings | |||
#-------------------------------------------------------------------------------------------------- | |||
# DATABASE | |||
# | |||
# IMPORTANT: the embedded H2 database is used by default. It is recommended for tests only. | |||
# Please use a production-ready database. Supported databases are MySQL, Oracle, PostgreSQL | |||
# and Microsoft SQLServer. | |||
# Permissions to create tables, indices and triggers must be granted to JDBC user. | |||
# The schema must be created first. | |||
sonar.jdbc.username=sonar | |||
sonar.jdbc.password=sonar | |||
#----- Embedded database H2 | |||
# Note: it does not accept connections from remote hosts, so the | |||
# SonarQube server and the maven plugin must be executed on the same host. | |||
# Comment the following line to deactivate the default embedded database. | |||
sonar.jdbc.url=jdbc:h2:tcp://localhost:9092/sonar | |||
# directory containing H2 database files. By default it's the /data directory in the SonarQube installation. | |||
#sonar.embeddedDatabase.dataDir= | |||
# H2 embedded database server listening port, defaults to 9092 | |||
#sonar.embeddedDatabase.port=9092 | |||
#----- MySQL 5.x | |||
# Comment the embedded database and uncomment the following line to use MySQL | |||
#sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true | |||
#----- Oracle 10g/11g | |||
# To connect to Oracle database: | |||
# | |||
# - It's recommended to use the latest version of the JDBC driver (ojdbc6.jar). | |||
# Download it in http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html | |||
# - Copy the driver to the directory extensions/jdbc-driver/oracle/ | |||
# - If you need to set the schema, please refer to http://jira.codehaus.org/browse/SONAR-5000 | |||
# - Comment the embedded database and uncomment the following line: | |||
#sonar.jdbc.url=jdbc:oracle:thin:@localhost/XE | |||
#----- PostgreSQL 8.x/9.x | |||
# Comment the embedded database and uncomment the following property to use PostgreSQL. | |||
# If you don't use the schema named "public", please refer to http://jira.codehaus.org/browse/SONAR-5000 | |||
#sonar.jdbc.url=jdbc:postgresql://localhost/sonar | |||
#----- Microsoft SQLServer | |||
# The Jtds open source driver is available in extensions/jdbc-driver/mssql. More details on http://jtds.sourceforge.net | |||
#sonar.jdbc.url=jdbc:jtds:sqlserver://localhost/sonar;SelectMethod=Cursor | |||
#----- Connection pool settings | |||
sonar.jdbc.maxActive=20 | |||
sonar.jdbc.maxIdle=5 | |||
sonar.jdbc.minIdle=2 | |||
sonar.jdbc.maxWait=5000 | |||
sonar.jdbc.minEvictableIdleTimeMillis=600000 | |||
sonar.jdbc.timeBetweenEvictionRunsMillis=30000 | |||
#-------------------------------------------------------------------------------------------------- | |||
# WEB SERVER | |||
# 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. | |||
#sonar.web.host=0.0.0.0 | |||
# Web context. When set, it must start with forward slash (for example /sonarqube). | |||
# The default value is root context (empty value). | |||
#sonar.web.context= | |||
# TCP port for incoming HTTP connections. Disabled when value is -1. | |||
#sonar.web.port=9000 | |||
# TCP port for incoming HTTPS connections. Disabled when value is -1 (default). | |||
#sonar.web.https.port=-1 | |||
# HTTPS - the alias used to for the server certificate in the keystore. | |||
# If not specified the first key read in the keystore is used. | |||
#sonar.web.https.keyAlias= | |||
# HTTPS - the password used to access the server certificate from the | |||
# specified keystore file. The default value is "changeit". | |||
#sonar.web.https.keyPass=changeit | |||
# HTTPS - the pathname of the keystore file where is stored the server certificate. | |||
# By default, the pathname is the file ".keystore" in the user home. | |||
# If keystoreType doesn't need a file use empty value. | |||
#sonar.web.https.keystoreFile= | |||
# HTTPS - the password used to access the specified keystore file. The default | |||
# value is the value of sonar.web.https.keyPass. | |||
#sonar.web.https.keystorePass= | |||
# HTTPS - the type of keystore file to be used for the server certificate. | |||
# The default value is JKS (Java KeyStore). | |||
#sonar.web.https.keystoreType=JKS | |||
# HTTPS - the name of the keystore provider to be used for the server certificate. | |||
# If not specified, the list of registered providers is traversed in preference order | |||
# and the first provider that supports the keystore type is used (see sonar.web.https.keystoreType). | |||
#sonar.web.https.keystoreProvider= | |||
# HTTPS - the pathname of the truststore file which contains trusted certificate authorities. | |||
# By default, this would be the cacerts file in your JRE. | |||
# If truststoreFile doesn't need a file use empty value. | |||
#sonar.web.https.truststoreFile= | |||
# HTTPS - the password used to access the specified truststore file. | |||
#sonar.web.https.truststorePass= | |||
# HTTPS - the type of truststore file to be used. | |||
# The default value is JKS (Java KeyStore). | |||
#sonar.web.https.truststoreType=JKS | |||
# HTTPS - the name of the truststore provider to be used for the server certificate. | |||
# If not specified, the list of registered providers is traversed in preference order | |||
# and the first provider that supports the truststore type is used (see sonar.web.https.truststoreType). | |||
#sonar.web.https.truststoreProvider= | |||
# HTTPS - whether to enable client certificate authentication. | |||
# The default is false (client certificates disabled). | |||
# Other possible values are 'want' (certificates will be requested, but not required), | |||
# and 'true' (certificates are required). | |||
#sonar.web.https.clientAuth=false | |||
# The maximum number of connections that the server will accept and process at any given time. | |||
# When this number has been reached, the server will not accept any more connections until | |||
# the number of connections falls below this value. The operating system may still accept connections | |||
# based on the sonar.web.connections.acceptCount property. The default value is 50 for each | |||
# enabled connector. | |||
#sonar.web.http.maxThreads=50 | |||
#sonar.web.https.maxThreads=50 | |||
# The minimum number of threads always kept running. The default value is 5 for each | |||
# enabled connector. | |||
#sonar.web.http.minThreads=5 | |||
#sonar.web.https.minThreads=5 | |||
# The maximum queue length for incoming connection requests when all possible request processing | |||
# threads are in use. Any requests received when the queue is full will be refused. | |||
# The default value is 25 for each enabled connector. | |||
#sonar.web.http.acceptCount=25 | |||
#sonar.web.https.acceptCount=25 | |||
# Access logs are generated in the file logs/access.log. This file is rolled over when it's 5Mb. | |||
# An archive of 3 files is kept in the same directory. | |||
# Access logs are enabled by default. | |||
#sonar.web.accessLogs.enable=true | |||
# TCP port for incoming AJP connections. Disabled when value is -1. | |||
# sonar.ajp.port=9009 | |||
#-------------------------------------------------------------------------------------------------- | |||
# UPDATE CENTER | |||
# The Update Center requires an internet connection to request http://update.sonarsource.org | |||
# It is enabled by default. | |||
#sonar.updatecenter.activate=true | |||
# HTTP proxy (default none) | |||
#http.proxyHost= | |||
#http.proxyPort= | |||
# NT domain name if NTLM proxy is used | |||
#http.auth.ntlm.domain= | |||
# SOCKS proxy (default none) | |||
#socksProxyHost= | |||
#socksProxyPort= | |||
# proxy authentication. The 2 following properties are used for HTTP and SOCKS proxies. | |||
#http.proxyUser= | |||
#http.proxyPassword= | |||
#-------------------------------------------------------------------------------------------------- | |||
# NOTIFICATIONS | |||
# Delay in seconds between processing of notification queue. Default is 60. | |||
#sonar.notifications.delay=60 | |||
#-------------------------------------------------------------------------------------------------- | |||
# PROFILING | |||
# Level of information displayed in the logs: NONE (default), BASIC (functional information) and FULL (functional and technical details) | |||
#sonar.log.profilingLevel=NONE | |||
#-------------------------------------------------------------------------------------------------- | |||
# DEVELOPMENT MODE | |||
# Only for debugging | |||
# Set to true to apply Ruby on Rails code changes on the fly | |||
#sonar.rails.dev=false |
@@ -0,0 +1,3 @@ | |||
hello: world | |||
foo=bar | |||
java.io.tmpdir=/should/be/overridden |
@@ -0,0 +1,19 @@ | |||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" | |||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd"> | |||
<id>test-jar-with-dependencies</id> | |||
<formats> | |||
<format>jar</format> | |||
</formats> | |||
<includeBaseDirectory>false</includeBaseDirectory> | |||
<dependencySets> | |||
<dependencySet> | |||
<outputDirectory>/</outputDirectory> | |||
<useProjectArtifact>true</useProjectArtifact> | |||
<!-- we're creating the test-jar as an attachement --> | |||
<useProjectAttachments>true</useProjectAttachments> | |||
<unpack>true</unpack> | |||
<scope>test</scope> | |||
</dependencySet> | |||
</dependencySets> | |||
</assembly> |
@@ -21,15 +21,14 @@ package org.sonar.search; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus; | |||
import org.elasticsearch.common.annotations.VisibleForTesting; | |||
import org.elasticsearch.common.settings.ImmutableSettings; | |||
import org.elasticsearch.common.unit.TimeValue; | |||
import org.elasticsearch.node.Node; | |||
import org.elasticsearch.node.NodeBuilder; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.process.ConfigurationUtils; | |||
import org.sonar.process.MinimumViableSystem; | |||
import org.sonar.process.MonitoredProcess; | |||
import org.sonar.process.ProcessEntryPoint; | |||
import org.sonar.process.ProcessLogging; | |||
import org.sonar.process.Props; | |||
import org.sonar.search.script.ListUpdate; | |||
@@ -40,7 +39,7 @@ import java.util.Collections; | |||
import java.util.HashSet; | |||
import java.util.Set; | |||
public class SearchServer extends MonitoredProcess { | |||
public class SearchServer implements MonitoredProcess { | |||
public static final String SONAR_NODE_NAME = "sonar.node.name"; | |||
public static final String ES_PORT_PROPERTY = "sonar.search.port"; | |||
@@ -55,27 +54,13 @@ public class SearchServer extends MonitoredProcess { | |||
private static final Integer MINIMUM_INDEX_REPLICATION = 1; | |||
private final Set<String> nodes = new HashSet<String>(); | |||
private final boolean isBlocking; | |||
private Node node; | |||
private final Props props; | |||
private final Object lock = new Object(); | |||
@VisibleForTesting | |||
public SearchServer(final Props props, boolean monitored, boolean blocking) { | |||
super(props, monitored); | |||
this.isBlocking = blocking; | |||
new MinimumViableSystem().check(); | |||
String esNodesInets = props.value(ES_CLUSTER_INET); | |||
if (StringUtils.isNotEmpty(esNodesInets)) { | |||
Collections.addAll(nodes, esNodesInets.split(",")); | |||
} | |||
} | |||
private Node node; | |||
public SearchServer(Props props) { | |||
super(props); | |||
this.isBlocking = true; | |||
this.props = props; | |||
new MinimumViableSystem().check(); | |||
String esNodesInets = props.value(ES_CLUSTER_INET); | |||
@@ -85,18 +70,8 @@ public class SearchServer extends MonitoredProcess { | |||
} | |||
@Override | |||
protected boolean doIsReady() { | |||
return node.client().admin().cluster().prepareHealth() | |||
.setWaitForYellowStatus() | |||
.setTimeout(TimeValue.timeValueSeconds(3L)) | |||
.get() | |||
.getStatus() != ClusterHealthStatus.RED; | |||
} | |||
@Override | |||
protected void doStart() { | |||
public void start() { | |||
synchronized (lock) { | |||
Integer port = props.valueAsInt(ES_PORT_PROPERTY); | |||
String clusterName = props.value(ES_CLUSTER_PROPERTY); | |||
@@ -169,17 +144,25 @@ public class SearchServer extends MonitoredProcess { | |||
.addMapping("_default_", "{\"dynamic\": \"strict\"}") | |||
.get(); | |||
} | |||
} | |||
if (isBlocking) { | |||
while (node != null && !node.isClosed()) { | |||
try { | |||
Thread.sleep(100); | |||
} catch (InterruptedException e) { | |||
// Ignore | |||
} | |||
boolean isReady() { | |||
return node.client().admin().cluster().prepareHealth() | |||
.setWaitForYellowStatus() | |||
.setTimeout(TimeValue.timeValueSeconds(3L)) | |||
.get() | |||
.getStatus() != ClusterHealthStatus.RED; | |||
} | |||
@Override | |||
public void awaitTermination() { | |||
while (node != null && !node.isClosed()) { | |||
try { | |||
Thread.sleep(200L); | |||
} catch (InterruptedException e) { | |||
// Ignore | |||
} | |||
} | |||
} | |||
private void initAnalysis(ImmutableSettings.Builder esSettings) { | |||
@@ -188,40 +171,40 @@ public class SearchServer extends MonitoredProcess { | |||
// Disallow dynamic mapping (too expensive) | |||
.put("index.mapper.dynamic", false) | |||
// Sortable text analyzer | |||
// Sortable text analyzer | |||
.put("index.analysis.analyzer.sortable.type", "custom") | |||
.put("index.analysis.analyzer.sortable.tokenizer", "keyword") | |||
.putArray("index.analysis.analyzer.sortable.filter", "trim", "lowercase", "truncate") | |||
// Edge NGram index-analyzer | |||
// Edge NGram index-analyzer | |||
.put("index.analysis.analyzer.index_grams.type", "custom") | |||
.put("index.analysis.analyzer.index_grams.tokenizer", "whitespace") | |||
.putArray("index.analysis.analyzer.index_grams.filter", "trim", "lowercase", "gram_filter") | |||
// Edge NGram search-analyzer | |||
// Edge NGram search-analyzer | |||
.put("index.analysis.analyzer.search_grams.type", "custom") | |||
.put("index.analysis.analyzer.search_grams.tokenizer", "whitespace") | |||
.putArray("index.analysis.analyzer.search_grams.filter", "trim", "lowercase") | |||
// Word index-analyzer | |||
// Word index-analyzer | |||
.put("index.analysis.analyzer.index_words.type", "custom") | |||
.put("index.analysis.analyzer.index_words.tokenizer", "standard") | |||
.putArray("index.analysis.analyzer.index_words.filter", | |||
"standard", "word_filter", "lowercase", "stop", "asciifolding", "porter_stem") | |||
// Word search-analyzer | |||
// Word search-analyzer | |||
.put("index.analysis.analyzer.search_words.type", "custom") | |||
.put("index.analysis.analyzer.search_words.tokenizer", "standard") | |||
.putArray("index.analysis.analyzer.search_words.filter", | |||
"standard", "lowercase", "stop", "asciifolding", "porter_stem") | |||
// Edge NGram filter | |||
// Edge NGram filter | |||
.put("index.analysis.filter.gram_filter.type", "edgeNGram") | |||
.put("index.analysis.filter.gram_filter.min_gram", 2) | |||
.put("index.analysis.filter.gram_filter.max_gram", 15) | |||
.putArray("index.analysis.filter.gram_filter.token_chars", "letter", "digit", "punctuation", "symbol") | |||
// Word filter | |||
// Word filter | |||
.put("index.analysis.filter.word_filter.type", "word_delimiter") | |||
.put("index.analysis.filter.word_filter.generate_word_parts", true) | |||
.put("index.analysis.filter.word_filter.catenate_words", true) | |||
@@ -232,7 +215,7 @@ public class SearchServer extends MonitoredProcess { | |||
.put("index.analysis.filter.word_filter.split_on_numerics", true) | |||
.put("index.analysis.filter.word_filter.stem_english_possessive", true) | |||
// Path Analyzer | |||
// Path Analyzer | |||
.put("index.analysis.analyzer.path_analyzer.type", "custom") | |||
.put("index.analysis.analyzer.path_analyzer.tokenizer", "path_hierarchy"); | |||
@@ -267,18 +250,18 @@ public class SearchServer extends MonitoredProcess { | |||
} | |||
@Override | |||
protected void doTerminate() { | |||
public void terminate() { | |||
synchronized (lock) { | |||
if (node != null && !node.isClosed()) { | |||
if (!node.isClosed()) { | |||
node.close(); | |||
node = null; | |||
} | |||
} | |||
} | |||
public static void main(String... args) { | |||
Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args); | |||
new ProcessLogging().configure(props, "/org/sonar/search/logback.xml"); | |||
new SearchServer(props).start(); | |||
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); | |||
new ProcessLogging().configure(entryPoint.getProps(), "/org/sonar/search/logback.xml"); | |||
SearchServer searchServer = new SearchServer(entryPoint.getProps()); | |||
entryPoint.launch(searchServer); | |||
} | |||
} |
@@ -26,16 +26,17 @@ import org.elasticsearch.common.settings.Settings; | |||
import org.elasticsearch.common.transport.InetSocketTransportAddress; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Ignore; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.process.JmxUtils; | |||
import org.sonar.process.MonitoredProcess; | |||
import org.sonar.process.Props; | |||
import javax.management.InstanceNotFoundException; | |||
import javax.management.MBeanRegistrationException; | |||
import javax.management.MBeanServer; | |||
import java.io.IOException; | |||
import java.lang.management.ManagementFactory; | |||
import java.net.ServerSocket; | |||
@@ -44,6 +45,7 @@ import java.util.Properties; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.junit.Assert.fail; | |||
@Ignore | |||
public class SearchServerTest { | |||
@Rule | |||
@@ -81,7 +83,6 @@ public class SearchServerTest { | |||
@Test | |||
public void server_fail_to_start() throws Exception { | |||
Properties properties = new Properties(); | |||
properties.setProperty(MonitoredProcess.NAME_PROPERTY, "ES"); | |||
searchServer = new SearchServer(new Props(properties)); | |||
new Thread(new Runnable() { | |||
@@ -107,7 +108,6 @@ public class SearchServerTest { | |||
@Test | |||
public void can_connect() throws Exception { | |||
Properties properties = new Properties(); | |||
properties.setProperty(MonitoredProcess.NAME_PROPERTY, "ES"); | |||
properties.setProperty(SearchServer.SONAR_PATH_DATA, temp.newFolder().getAbsolutePath()); | |||
properties.setProperty(SearchServer.SONAR_PATH_TEMP, temp.newFolder().getAbsolutePath()); | |||
properties.setProperty(SearchServer.SONAR_PATH_LOG, temp.newFolder().getAbsolutePath()); |
@@ -19,22 +19,32 @@ | |||
*/ | |||
package org.sonar.server.app; | |||
import com.google.common.base.Throwables; | |||
import com.google.common.util.concurrent.Uninterruptibles; | |||
import org.apache.catalina.Container; | |||
import org.apache.catalina.Executor; | |||
import org.apache.catalina.Lifecycle; | |||
import org.apache.catalina.LifecycleEvent; | |||
import org.apache.catalina.LifecycleListener; | |||
import org.apache.catalina.LifecycleState; | |||
import org.apache.catalina.Server; | |||
import org.apache.catalina.Service; | |||
import org.apache.catalina.connector.Connector; | |||
import org.apache.catalina.core.StandardContext; | |||
import org.apache.catalina.startup.Tomcat; | |||
import org.apache.commons.io.FileUtils; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.process.ProcessUtils; | |||
import org.sonar.process.Props; | |||
import org.sonar.process.Terminable; | |||
import java.io.File; | |||
import java.util.concurrent.TimeUnit; | |||
class EmbeddedTomcat implements Terminable { | |||
class EmbeddedTomcat { | |||
private final Props props; | |||
private Tomcat tomcat = null; | |||
private Thread hook = null; | |||
private boolean ready = false; | |||
private volatile StandardContext webappContext; | |||
EmbeddedTomcat(Props props) { | |||
this.props = props; | |||
@@ -62,45 +72,52 @@ class EmbeddedTomcat implements Terminable { | |||
tomcat.getHost().setDeployOnStartup(true); | |||
Logging.configure(tomcat, props); | |||
Connectors.configure(tomcat, props); | |||
StandardContext webappContext = Webapp.configure(tomcat, props); | |||
ProcessUtils.addSelfShutdownHook(this); | |||
webappContext = Webapp.configure(tomcat, props); | |||
tomcat.start(); | |||
waitForWebappReady(); | |||
if (webappContext.getState().isAvailable()) { | |||
ready = true; | |||
tomcat.getServer().await(); | |||
} | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Fail to start web server", e); | |||
} finally { | |||
// Failed to start or received a shutdown command (should never occur as shutdown port is disabled) | |||
terminate(); | |||
Throwables.propagate(e); | |||
} | |||
} | |||
private File tomcatBasedir() { | |||
return new File(props.value("sonar.path.temp"), "tc"); | |||
private void waitForWebappReady() { | |||
while (true) { | |||
switch (webappContext.getState()) { | |||
case NEW: | |||
case INITIALIZING: | |||
case INITIALIZED: | |||
case STARTING_PREP: | |||
case STARTING: | |||
Uninterruptibles.sleepUninterruptibly(300L, TimeUnit.MILLISECONDS); | |||
break; | |||
case STARTED: | |||
// ok | |||
return; | |||
default: | |||
// problem, stopped or failed | |||
throw new IllegalStateException("YYY Webapp did not start"); | |||
} | |||
} | |||
} | |||
boolean isReady() { | |||
return ready && tomcat != null; | |||
private File tomcatBasedir() { | |||
return new File(props.value("sonar.path.temp"), "tc"); | |||
} | |||
@Override | |||
public void terminate() { | |||
if (tomcat != null) { | |||
synchronized (tomcat) { | |||
if (tomcat.getServer().getState().isAvailable()) { | |||
try { | |||
tomcat.stop(); | |||
tomcat.destroy(); | |||
} catch (Exception e) { | |||
LoggerFactory.getLogger(EmbeddedTomcat.class).error("Fail to stop web service", e); | |||
} | |||
} | |||
void terminate() { | |||
if (tomcat.getServer().getState().isAvailable()) { | |||
try { | |||
tomcat.stop(); | |||
tomcat.destroy(); | |||
} catch (Exception e) { | |||
LoggerFactory.getLogger(EmbeddedTomcat.class).error("Fail to stop web server", e); | |||
} | |||
} | |||
ready = false; | |||
FileUtils.deleteQuietly(tomcatBasedir()); | |||
} | |||
void awaitTermination() { | |||
tomcat.getServer().await(); | |||
} | |||
} |
@@ -19,18 +19,16 @@ | |||
*/ | |||
package org.sonar.server.app; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.process.ConfigurationUtils; | |||
import org.sonar.process.MinimumViableSystem; | |||
import org.sonar.process.MonitoredProcess; | |||
import org.sonar.process.ProcessEntryPoint; | |||
import org.sonar.process.Props; | |||
public class WebServer extends MonitoredProcess { | |||
public class WebServer implements MonitoredProcess { | |||
private final EmbeddedTomcat tomcat; | |||
WebServer(Props props) throws Exception { | |||
super(props); | |||
new MinimumViableSystem() | |||
.setRequiredJavaOption("file.encoding", "UTF-8") | |||
.check(); | |||
@@ -38,32 +36,27 @@ public class WebServer extends MonitoredProcess { | |||
} | |||
@Override | |||
protected void doStart() { | |||
try { | |||
tomcat.start(); | |||
} catch (Exception e) { | |||
LoggerFactory.getLogger(getClass()).error("TC error", e); | |||
} finally { | |||
terminate(); | |||
} | |||
public void start() { | |||
tomcat.start(); | |||
} | |||
@Override | |||
protected void doTerminate() { | |||
public void terminate() { | |||
tomcat.terminate(); | |||
} | |||
@Override | |||
protected boolean doIsReady() { | |||
return tomcat.isReady(); | |||
public void awaitTermination() { | |||
tomcat.awaitTermination(); | |||
} | |||
/** | |||
* Can't be started as is. Needs to be bootstrapped by sonar-application | |||
*/ | |||
public static void main(String[] args) throws Exception { | |||
Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args); | |||
Logging.init(props); | |||
new WebServer(props).start(); | |||
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); | |||
Logging.init(entryPoint.getProps()); | |||
WebServer server = new WebServer(entryPoint.getProps()); | |||
entryPoint.launch(server); | |||
} | |||
} |
@@ -64,7 +64,6 @@ class Webapp { | |||
String key = entry.getKey().toString(); | |||
context.addParameter(key, entry.getValue().toString()); | |||
} | |||
return context; | |||
} catch (Exception e) { |
@@ -19,7 +19,7 @@ | |||
*/ | |||
package org.sonar.server.platform; | |||
import org.slf4j.LoggerFactory; | |||
import com.google.common.base.Throwables; | |||
import javax.servlet.ServletContext; | |||
import javax.servlet.ServletContextEvent; | |||
@@ -46,9 +46,9 @@ public final class PlatformServletContextListener implements ServletContextListe | |||
// - server does not stop if webapp fails at startup | |||
// - the second listener for jruby on rails is started even if this listener fails. It generates | |||
// unexpected errors | |||
LoggerFactory.getLogger(getClass()).error("Fail to start server", t); | |||
// LoggerFactory.getLogger(getClass()).error("Fail to start server", t); | |||
stopQuietly(); | |||
throw new IllegalStateException("Fail to start webapp", t); | |||
throw Throwables.propagate(t); | |||
} | |||
} | |||
@@ -211,9 +211,11 @@ public class ServerPluginJarsInstaller { | |||
private void deploy(DefaultPluginMetadata plugin) { | |||
LOG.info("Deploy plugin {}", Joiner.on(" / ").skipNulls().join(plugin.getName(), plugin.getVersion(), plugin.getImplementationBuild())); | |||
Preconditions.checkState(plugin.isCompatibleWith(server.getVersion()), | |||
"Plugin %s needs a more recent version of SonarQube than %s. At least %s is expected", | |||
plugin.getKey(), server.getVersion(), plugin.getSonarVersion()); | |||
if (!plugin.isCompatibleWith(server.getVersion())) { | |||
throw MessageException.of(String.format( | |||
"Plugin %s needs a more recent version of SonarQube than %s. At least %s is expected", | |||
plugin.getKey(), server.getVersion(), plugin.getSonarVersion())); | |||
} | |||
try { | |||
File pluginDeployDir = new File(fs.getDeployedPluginsDir(), plugin.getKey()); |
@@ -85,7 +85,7 @@ public class ServerPluginJarsInstallerTest { | |||
jarsInstaller.install(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); | |||
assertThat(new File(pluginsDir, "foo-plugin-1.0.jar")).exists().isFile(); | |||
PluginMetadata plugin = jarsInstaller.getMetadata("foo"); | |||
assertThat(plugin.getName()).isEqualTo("Foo"); | |||
@@ -101,7 +101,7 @@ public class ServerPluginJarsInstallerTest { | |||
jarsInstaller.install(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).isEmpty(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).isEmpty(); | |||
} | |||
@Test | |||
@@ -115,7 +115,7 @@ public class ServerPluginJarsInstallerTest { | |||
jarsInstaller.install(); | |||
// do not copy foo 1.0 | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(2); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(2); | |||
assertThat(new File(pluginsDir, "foo-plugin-2.0.jar")).exists().isFile(); | |||
assertThat(new File(pluginsDir, "bar-plugin-1.0.jar")).exists().isFile(); | |||
PluginMetadata plugin = jarsInstaller.getMetadata("foo"); | |||
@@ -138,7 +138,7 @@ public class ServerPluginJarsInstallerTest { | |||
assertThat(plugin.isUseChildFirstClassLoader()).isFalse(); | |||
// check that the file is still present in extensions/plugins | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); | |||
assertThat(new File(pluginsDir, "foo-plugin-1.0.jar")).exists().isFile(); | |||
} | |||
@@ -151,13 +151,13 @@ public class ServerPluginJarsInstallerTest { | |||
// nothing to install but keep the file | |||
assertThat(jarsInstaller.getMetadata()).isEmpty(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); | |||
assertThat(new File(pluginsDir, "not-a-plugin.jar")).exists().isFile(); | |||
} | |||
@Test | |||
public void fail_if_plugin_requires_greater_SQ_version() throws Exception { | |||
exception.expect(IllegalStateException.class); | |||
exception.expect(MessageException.class); | |||
exception.expectMessage("Plugin switchoffviolations needs a more recent version of SonarQube than 2.0. At least 2.5 is expected"); | |||
when(upgradeStatus.isFreshInstall()).thenReturn(false); | |||
@@ -174,8 +174,8 @@ public class ServerPluginJarsInstallerTest { | |||
jarsInstaller.install(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(downloadsDir, new String[]{"jar"}, false)).isEmpty(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(downloadsDir, new String[] {"jar"}, false)).isEmpty(); | |||
assertThat(new File(pluginsDir, "foo-plugin-1.0.jar")).exists().isFile(); | |||
} | |||
@@ -187,8 +187,8 @@ public class ServerPluginJarsInstallerTest { | |||
jarsInstaller.install(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(downloadsDir, new String[]{"jar"}, false)).isEmpty(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(downloadsDir, new String[] {"jar"}, false)).isEmpty(); | |||
assertThat(new File(pluginsDir, "foo-plugin-2.0.jar")).exists().isFile(); | |||
} | |||
@@ -205,8 +205,8 @@ public class ServerPluginJarsInstallerTest { | |||
PluginMetadata plugin = jarsInstaller.getMetadata("foo"); | |||
assertThat(plugin).isNotNull(); | |||
assertThat(plugin.getVersion()).isEqualTo("2.0"); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(downloadsDir, new String[]{"jar"}, false)).isEmpty(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(downloadsDir, new String[] {"jar"}, false)).isEmpty(); | |||
File installed = new File(pluginsDir, "foo-plugin-1.0.jar"); | |||
assertThat(installed).exists().isFile(); | |||
} | |||
@@ -218,7 +218,7 @@ public class ServerPluginJarsInstallerTest { | |||
jarsInstaller.install(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).isEmpty(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).isEmpty(); | |||
assertThat(trashDir).doesNotExist(); | |||
} | |||
@@ -247,8 +247,8 @@ public class ServerPluginJarsInstallerTest { | |||
jarsInstaller.install(); | |||
jarsInstaller.uninstall("foo"); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).isEmpty(); | |||
assertThat(FileUtils.listFiles(trashDir, new String[]{"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).isEmpty(); | |||
assertThat(FileUtils.listFiles(trashDir, new String[] {"jar"}, false)).hasSize(1); | |||
assertThat(jarsInstaller.getUninstalls()).containsOnly("foo-plugin-1.0.jar"); | |||
} | |||
@@ -261,8 +261,8 @@ public class ServerPluginJarsInstallerTest { | |||
jarsInstaller.uninstall("foo"); | |||
jarsInstaller.cancelUninstalls(); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(trashDir, new String[]{"jar"}, false)).hasSize(0); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(trashDir, new String[] {"jar"}, false)).hasSize(0); | |||
assertThat(jarsInstaller.getUninstalls()).isEmpty(); | |||
} | |||
@@ -274,10 +274,10 @@ public class ServerPluginJarsInstallerTest { | |||
jarsInstaller.install(); | |||
// do not deploy in extensions/plugins | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(0); | |||
assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(0); | |||
// do not remove from lib/core-plugins | |||
assertThat(FileUtils.listFiles(coreDir, new String[]{"jar"}, false)).hasSize(1); | |||
assertThat(FileUtils.listFiles(coreDir, new String[] {"jar"}, false)).hasSize(1); | |||
PluginMetadata plugin = jarsInstaller.getMetadata("foo"); | |||
assertThat(plugin).isNotNull(); |
@@ -61,10 +61,9 @@ public class BaseIndexTest { | |||
Properties properties = new Properties(); | |||
properties.setProperty(IndexProperties.CLUSTER_NAME, clusterName); | |||
properties.setProperty(IndexProperties.NODE_PORT, clusterPort.toString()); | |||
properties.setProperty(MonitoredProcess.NAME_PROPERTY, "ES"); | |||
properties.setProperty(SearchServer.SONAR_PATH_HOME, temp.getRoot().getAbsolutePath()); | |||
try { | |||
searchServer = new SearchServer(new Props(properties), false, false); | |||
searchServer = new SearchServer(new Props(properties)); | |||
} catch (Exception e) { | |||
e.printStackTrace(); | |||
} |
@@ -26,7 +26,6 @@ import org.apache.commons.lang.StringUtils; | |||
import org.junit.rules.ExternalResource; | |||
import org.sonar.api.database.DatabaseProperties; | |||
import org.sonar.api.resources.Language; | |||
import org.sonar.process.MonitoredProcess; | |||
import org.sonar.process.NetworkUtils; | |||
import org.sonar.process.Props; | |||
import org.sonar.search.SearchServer; | |||
@@ -35,6 +34,7 @@ import org.sonar.server.search.IndexProperties; | |||
import org.sonar.server.ws.WsTester; | |||
import javax.annotation.Nullable; | |||
import java.io.File; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
@@ -71,13 +71,8 @@ public class ServerTester extends ExternalResource { | |||
Properties properties = new Properties(); | |||
properties.setProperty(IndexProperties.CLUSTER_NAME, clusterName); | |||
properties.setProperty(IndexProperties.NODE_PORT, clusterPort.toString()); | |||
properties.setProperty(MonitoredProcess.NAME_PROPERTY, "ES"); | |||
properties.setProperty(SearchServer.SONAR_PATH_HOME, homeDir.getAbsolutePath()); | |||
try { | |||
searchServer = new SearchServer(new Props(properties), false, false); | |||
} catch (Exception e) { | |||
e.printStackTrace(); | |||
} | |||
searchServer = new SearchServer(new Props(properties)); | |||
} | |||
/** | |||
@@ -99,7 +94,6 @@ public class ServerTester extends ExternalResource { | |||
properties.setProperty(IndexProperties.CLUSTER_NAME, clusterName); | |||
properties.setProperty(IndexProperties.NODE_PORT, clusterPort.toString()); | |||
properties.setProperty(MonitoredProcess.NAME_PROPERTY, "ES"); | |||
properties.setProperty("sonar.path.home", homeDir.getAbsolutePath()); | |||
properties.setProperty(DatabaseProperties.PROP_URL, "jdbc:h2:" + homeDir.getAbsolutePath() + "/h2"); |
@@ -25,6 +25,11 @@ | |||
<artifactId>sonar-process</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-process-monitor</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.google.code.findbugs</groupId> |
@@ -21,19 +21,18 @@ package org.sonar.application; | |||
import org.apache.commons.io.FilenameUtils; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.process.JmxUtils; | |||
import org.sonar.process.MinimumViableSystem; | |||
import org.sonar.process.Monitor; | |||
import org.sonar.process.ProcessLogging; | |||
import org.sonar.process.ProcessMXBean; | |||
import org.sonar.process.ProcessUtils; | |||
import org.sonar.process.ProcessWrapper; | |||
import org.sonar.process.Props; | |||
import org.sonar.search.SearchServer; | |||
import org.sonar.process.State; | |||
import org.sonar.process.monitor.JavaCommand; | |||
import org.sonar.process.monitor.Monitor; | |||
import java.io.File; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Properties; | |||
/** | |||
@@ -41,135 +40,88 @@ import java.util.Properties; | |||
*/ | |||
public class App implements ProcessMXBean { | |||
private Monitor monitor = new Monitor(); | |||
private ProcessWrapper elasticsearch; | |||
private ProcessWrapper server; | |||
private boolean success = false; | |||
private final Monitor monitor; | |||
public App() { | |||
JmxUtils.registerMBean(this, "SonarQube"); | |||
ProcessUtils.addSelfShutdownHook(this); | |||
this(Monitor.create()); | |||
} | |||
public void start(Props props) throws InterruptedException { | |||
try { | |||
Logger logger = LoggerFactory.getLogger(getClass()); | |||
App(Monitor monitor) { | |||
this.monitor = monitor; | |||
JmxUtils.registerMBean(this, "SonarQube"); | |||
} | |||
monitor.start(); | |||
public void start(Props props) { | |||
monitor.start(createCommands(props)); | |||
monitor.awaitTermination(); | |||
} | |||
File homeDir = props.nonNullValueAsFile("sonar.path.home"); | |||
File tempDir = props.nonNullValueAsFile("sonar.path.temp"); | |||
elasticsearch = new ProcessWrapper(JmxUtils.SEARCH_SERVER_NAME); | |||
elasticsearch | |||
private List<JavaCommand> createCommands(Props props) { | |||
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); | |||
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") | |||
.setArguments(props.rawProperties()) | |||
.addClasspath("./lib/common/*") | |||
.addClasspath("./lib/search/*"); | |||
commands.add(elasticsearch); | |||
// 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) | |||
.setWorkDir(homeDir) | |||
.setJmxPort(props.valueAsInt(DefaultSettings.SEARCH_JMX_PORT)) | |||
.addJavaOpts(props.value(DefaultSettings.SEARCH_JAVA_OPTS)) | |||
.setTempDirectory(tempDir.getAbsoluteFile()) | |||
.setClassName("org.sonar.search.SearchServer") | |||
.addProperties(props.rawProperties()) | |||
.setJmxPort(props.valueAsInt(DefaultSettings.WEB_JMX_PORT)) | |||
.addJavaOptions(props.nonNullValue(DefaultSettings.WEB_JAVA_OPTS)) | |||
.setTempDir(tempDir.getAbsoluteFile()) | |||
// required for logback tomcat valve | |||
.setEnvVariable("sonar.path.logs", props.nonNullValue("sonar.path.logs")) | |||
.setClassName("org.sonar.server.app.WebServer") | |||
.setArguments(props.rawProperties()) | |||
.addClasspath("./lib/common/*") | |||
.addClasspath("./lib/search/*"); | |||
if (elasticsearch.execute()) { | |||
monitor.monitor(elasticsearch); | |||
if (elasticsearch.waitForReady()) { | |||
logger.info("search server is up"); | |||
// do not yet start SQ in cluster mode. See SONAR-5483 & SONAR-5391 | |||
if (StringUtils.isEmpty(props.value(DefaultSettings.CLUSTER_MASTER))) { | |||
server = new ProcessWrapper(JmxUtils.WEB_SERVER_NAME) | |||
.setWorkDir(homeDir) | |||
.setJmxPort(props.valueAsInt(DefaultSettings.WEB_JMX_PORT)) | |||
.addJavaOpts(props.nonNullValue(DefaultSettings.WEB_JAVA_OPTS)) | |||
.setTempDirectory(tempDir.getAbsoluteFile()) | |||
// required for logback tomcat valve | |||
.setLogDir(props.nonNullValueAsFile("sonar.path.logs")) | |||
.setClassName("org.sonar.server.app.WebServer") | |||
.addProperties(props.rawProperties()) | |||
.addClasspath("./lib/common/*") | |||
.addClasspath("./lib/server/*"); | |||
String driverPath = props.value(JdbcSettings.PROPERTY_DRIVER_PATH); | |||
if (driverPath != null) { | |||
server.addClasspath(driverPath); | |||
} | |||
if (server.execute()) { | |||
monitor.monitor(server); | |||
if (server.waitForReady()) { | |||
success = true; | |||
logger.info("web server is up"); | |||
} | |||
} | |||
} else { | |||
success = true; | |||
} | |||
} | |||
.addClasspath("./lib/server/*"); | |||
String driverPath = props.value(JdbcSettings.PROPERTY_DRIVER_PATH); | |||
if (driverPath != null) { | |||
webServer.addClasspath(driverPath); | |||
} | |||
} finally { | |||
monitor.join(); | |||
terminate(); | |||
commands.add(webServer); | |||
} | |||
return commands; | |||
} | |||
static String starPath(File homeDir, String relativePath) { | |||
File dir = new File(homeDir, relativePath); | |||
return FilenameUtils.concat(dir.getAbsolutePath(), "*"); | |||
@Override | |||
public void terminate() { | |||
monitor.stop(); | |||
} | |||
@Override | |||
public boolean isReady() { | |||
return monitor.isAlive(); | |||
return monitor.getState() == State.STARTED; | |||
} | |||
@Override | |||
public long ping() { | |||
return System.currentTimeMillis(); | |||
} | |||
public void ping() { | |||
@Override | |||
public void terminate() { | |||
if (monitor != null && monitor.isAlive()) { | |||
monitor.terminate(); | |||
monitor = null; | |||
} | |||
if (server != null) { | |||
server.terminate(); | |||
server = null; | |||
} | |||
if (elasticsearch != null) { | |||
elasticsearch.terminate(); | |||
elasticsearch = null; | |||
} | |||
} | |||
private boolean isSuccess() { | |||
return success; | |||
static String starPath(File homeDir, String relativePath) { | |||
File dir = new File(homeDir, relativePath); | |||
return FilenameUtils.concat(dir.getAbsolutePath(), "*"); | |||
} | |||
public static void main(String[] args) { | |||
public static void main(String[] args) throws Exception { | |||
new MinimumViableSystem().check(); | |||
CommandLineParser cli = new CommandLineParser(); | |||
Properties rawProperties = cli.parseArguments(args); | |||
Props props; | |||
try { | |||
props = new PropsBuilder(rawProperties, new JdbcSettings()).build(); | |||
new ProcessLogging().configure(props, "/org/sonar/application/logback.xml"); | |||
} catch (Exception e) { | |||
throw new IllegalStateException(e); | |||
} | |||
Props props = new PropsBuilder(rawProperties, new JdbcSettings()).build(); | |||
new ProcessLogging().configure(props, "/org/sonar/application/logback.xml"); | |||
App app = new App(); | |||
ProcessUtils.addSelfShutdownHook(app); | |||
try { | |||
// start and wait for shutdown command | |||
if (props.contains(SearchServer.ES_CLUSTER_INET)) { | |||
LoggerFactory.getLogger(App.class).info("SonarQube slave configured to join SonarQube master : {}", props.value(SearchServer.ES_CLUSTER_INET)); | |||
} | |||
app.start(props); | |||
} catch (InterruptedException e) { | |||
LoggerFactory.getLogger(App.class).info("interrupted"); | |||
} finally { | |||
LoggerFactory.getLogger(App.class).info("stopped"); | |||
System.exit(app.isSuccess() ? 0 : 1); | |||
} | |||
app.start(props); | |||
} | |||
} |