diff options
Diffstat (limited to 'server')
44 files changed, 1164 insertions, 665 deletions
diff --git a/server/sonar-process/pom.xml b/server/sonar-process/pom.xml index d9626a43316..457068b014c 100644 --- a/server/sonar-process/pom.xml +++ b/server/sonar-process/pom.xml @@ -38,10 +38,6 @@ <artifactId>commons-lang</artifactId> </dependency> <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - </dependency> - <dependency> <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> <scope>provided</scope> @@ -67,5 +63,10 @@ <artifactId>hamcrest-all</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java b/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java index b17de206349..5b8102c044d 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java +++ b/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java @@ -20,8 +20,6 @@ package org.sonar.process; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Throwables; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; @@ -61,8 +59,10 @@ final class AesCipher extends Cipher { 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 Throwables.propagate(e); + throw new RuntimeException(e); } } @@ -73,8 +73,10 @@ final class AesCipher extends Cipher { 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 Throwables.propagate(e); + throw new RuntimeException(e); } } @@ -95,7 +97,6 @@ final class AesCipher extends Cipher { return loadSecretFileFromFile(path); } - @VisibleForTesting Key loadSecretFileFromFile(@Nullable String path) throws IOException { if (StringUtils.isBlank(path)) { throw new IllegalStateException("Secret key not found. Please set the property " + ENCRYPTION_SECRET_KEY_PATH); @@ -123,7 +124,6 @@ final class AesCipher extends Cipher { } } - @VisibleForTesting String getPathToSecretKey() { if (StringUtils.isBlank(pathToSecretKey)) { pathToSecretKey = new File(FileUtils.getUserDirectoryPath(), ".sonar/sonar-secret.txt").getPath(); diff --git a/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java b/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java index ba2c1f29f47..5430aa26173 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java @@ -19,8 +19,12 @@ */ package org.sonar.process; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.text.StrSubstitutor; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; import java.util.Enumeration; import java.util.Map; import java.util.Properties; @@ -31,11 +35,7 @@ public final class ConfigurationUtils { // Utility class } - static Properties interpolateEnvVariables(Properties properties) { - return interpolateVariables(properties, System.getenv()); - } - - static Properties interpolateVariables(Properties properties, Map<String, String> variables) { + public static Properties interpolateVariables(Properties properties, Map<String, String> variables) { Properties result = new Properties(); Enumeration keys = properties.keys(); while (keys.hasMoreElements()) { @@ -46,4 +46,28 @@ public final class ConfigurationUtils { } return result; } + + public static Props loadPropsFromCommandLineArgs(String[] args) { + if (args.length != 1) { + throw new IllegalStateException("Only a single command-line argument is accepted " + + "(absolute path to configuration file)"); + } + + File propertyFile = new File(args[0]); + if (!propertyFile.exists()) { + throw new IllegalStateException("Property file '" + args[0] + "' does not exist! "); + } + + Properties properties = new Properties(); + FileReader reader = null; + try { + reader = new FileReader(propertyFile); + properties.load(reader); + } catch (IOException e) { + throw new IllegalStateException("Could not read properties from file '" + args[0] + "'", e); + } finally { + IOUtils.closeQuietly(reader); + } + return new Props(properties); + } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/Encryption.java b/server/sonar-process/src/main/java/org/sonar/process/Encryption.java index 0de37cc4456..cca05e6c780 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/Encryption.java +++ b/server/sonar-process/src/main/java/org/sonar/process/Encryption.java @@ -20,9 +20,8 @@ package org.sonar.process; -import com.google.common.collect.ImmutableMap; - import javax.annotation.Nullable; +import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; @@ -38,14 +37,13 @@ public final class Encryption { private static final String AES_ALGORITHM = "aes"; private final AesCipher aesCipher; - private final Map<String, Cipher> ciphers; + 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 = ImmutableMap.of( - BASE64_ALGORITHM, new Base64Cipher(), - AES_ALGORITHM, aesCipher); + ciphers.put(BASE64_ALGORITHM, new Base64Cipher()); + ciphers.put(AES_ALGORITHM, aesCipher); } public boolean isEncrypted(String value) { diff --git a/server/sonar-process/src/main/java/org/sonar/process/Monitor.java b/server/sonar-process/src/main/java/org/sonar/process/Monitor.java index a01a49e1745..75fd0ef8200 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/Monitor.java +++ b/server/sonar-process/src/main/java/org/sonar/process/Monitor.java @@ -33,29 +33,31 @@ import java.util.concurrent.TimeUnit; public class Monitor extends Thread { + private static final long MAX_TIME = 15000L; + private final static Logger LOGGER = LoggerFactory.getLogger(Monitor.class); private volatile List<ProcessWrapper> processes; private volatile Map<String, Long> pings; + private ProcessWatch processWatch; private ScheduledFuture<?> watch; - private final ScheduledExecutorService monitor; + private ScheduledExecutorService monitor; public Monitor() { processes = new ArrayList<ProcessWrapper>(); pings = new HashMap<String, Long>(); monitor = Executors.newScheduledThreadPool(1); - watch = monitor.scheduleWithFixedDelay(new ProcessWatch(), 0, 3, TimeUnit.SECONDS); + processWatch = new ProcessWatch(); + watch = monitor.scheduleWithFixedDelay(processWatch, 0, 3, TimeUnit.SECONDS); } public void registerProcess(ProcessWrapper processWrapper) { - LOGGER.trace("Monitor::registerProcess() START"); processes.add(processWrapper); pings.put(processWrapper.getName(), System.currentTimeMillis()); processWrapper.start(); - for(int i=0; i<10; i++){ - if(processWrapper.getProcessMXBean() == null - || !processWrapper.getProcessMXBean().isReady()){ + for (int i = 0; i < 10; i++) { + if (processWrapper.getProcessMXBean() == null || !processWrapper.getProcessMXBean().isReady()) { try { Thread.sleep(500L); } catch (InterruptedException e) { @@ -63,17 +65,17 @@ public class Monitor extends Thread { } } } - LOGGER.trace("Monitor::registerProcess() END"); } private class ProcessWatch implements Runnable { public void run() { - LOGGER.trace("Monitor::ProcessWatch PINGING for map: {}", processes); for (ProcessWrapper process : processes) { try { - long time = process.getProcessMXBean().ping(); - LOGGER.debug("Monitor::ProcessWatch PINGED '{}'", process.getName()); - pings.put(process.getName(), time); + if (process.getProcessMXBean() != null) { + long time = process.getProcessMXBean().ping(); + LOGGER.debug("PINGED '{}'", process.getName()); + pings.put(process.getName(), time); + } } catch (Exception e) { LOGGER.error("Error while pinging {}", process.getName(), e); } @@ -83,32 +85,34 @@ public class Monitor extends Thread { private boolean processIsValid(ProcessWrapper process) { long now = System.currentTimeMillis(); - LOGGER.debug("Monitor::processIsValid() -- Time since last ping for '{}': {}ms", - process.getName(), (now - pings.get(process.getName()))); - return (now - pings.get(process.getName())) < 5000L; + return (now - pings.get(process.getName())) < MAX_TIME; } public void run() { - LOGGER.trace("Monitor::run() START"); - boolean everythingOK = true; - while (everythingOK) { - for(ProcessWrapper process: processes){ - if(!processIsValid(process)){ - LOGGER.warn("Monitor::run() -- Process '{}' is not valid. Exiting monitor", process.getName()); - everythingOK = false; - break; + try { + while (true) { + for (ProcessWrapper process : processes) { + if (!processIsValid(process)) { + LOGGER.warn("Monitor::run() -- Process '{}' is not valid. Exiting monitor", process.getName()); + this.interrupt(); + } } + Thread.sleep(3000L); } - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - LOGGER.warn("Monitoring thread has been interrupted. Closing"); - watch.cancel(true); - monitor.shutdownNow(); - } + } catch (InterruptedException e) { + LOGGER.debug("Monitoring thread is interrupted."); + } finally { + terminate(); + } + } + + public void terminate() { + if (monitor != null) { + monitor.shutdownNow(); + watch.cancel(true); + watch = null; + processWatch = null; } - watch.cancel(true); - monitor.shutdownNow(); - LOGGER.trace("Monitor::run() END"); } + } diff --git a/server/sonar-process/src/main/java/org/sonar/process/Process.java b/server/sonar-process/src/main/java/org/sonar/process/Process.java index 045a5dcd0b9..ddec37d7590 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/Process.java +++ b/server/sonar-process/src/main/java/org/sonar/process/Process.java @@ -19,7 +19,6 @@ */ package org.sonar.process; -import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,11 +29,7 @@ import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; import java.lang.management.ManagementFactory; -import java.util.Properties; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -42,31 +37,22 @@ import java.util.concurrent.TimeUnit; public abstract class Process implements ProcessMXBean { - public static final String SONAR_HOME = "SONAR_HOME"; - - public static final String JAVA_OPS = "javaOps"; public static final String NAME_PROPERTY = "pName"; public static final String PORT_PROPERTY = "pPort"; - public static final String MISSING_NAME_ARGUMENT = "Missing Name argument"; - public static final String SONAR_HOME_IS_NOT_SET = "variable SONAR_HOME is not set."; - public static final String SONAR_HOME_DOES_NOT_EXIST = "Directory SONAR_HOME does not exist"; - public static final String SONAR_HOME_IS_NOT_WRITABLE = "Directory SONAR_HOME is not writable"; + protected final static Logger LOGGER = LoggerFactory.getLogger(Process.class); - private final static Logger LOGGER = LoggerFactory.getLogger(Process.class); + private Long lastPing; - protected Long lastPing; - - String name; - Integer port; + private String name; + private Integer port; protected final Props props; - private Thread shutdownHook; - private static final long MAX_ALLOWED_TIME = 3000L; + private static final long MAX_ALLOWED_TIME = 15000L; private ScheduledFuture<?> pingTask = null; - final ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1); + private ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1); final Runnable breakOnMissingPing = new Runnable() { public void run() { long time = System.currentTimeMillis(); @@ -78,41 +64,15 @@ public abstract class Process implements ProcessMXBean { } }; - public Process(String[] args) { - // loading arguments from file and system. - if (args.length < 1) { - throw new IllegalStateException("Process is missing argument!"); - } - - File propertyFile = new File(args[0]); - if (!propertyFile.exists()) { - throw new IllegalStateException("Property file '" + args[0] + "' does not exist! "); - } - - Properties properties = new Properties(); - try { - properties.load(new FileReader(propertyFile)); - } catch (IOException e) { - throw new IllegalStateException("Could not read properties from file '" + args[0] + "'", e); - } - props = Props.create(properties); - init(); - } - - @VisibleForTesting - public Process(Props props) { + protected Process(Props props) { this.props = props; init(); } private void init() { - - // Loading all Properties from file this.name = props.of(NAME_PROPERTY, null); this.port = props.intOf(PORT_PROPERTY); - validateSonarHome(props); - // Testing required properties if (StringUtils.isEmpty(this.name)) { throw new IllegalStateException(MISSING_NAME_ARGUMENT); @@ -129,17 +89,10 @@ public abstract class Process implements ProcessMXBean { throw new IllegalStateException("Process is not a compliant MBean", e); } - - shutdownHook = new Thread(new Runnable() { + Thread shutdownHook = new Thread(new Runnable() { @Override public void run() { - LOGGER.trace("Process[{}]::ShutdownHook::run() START", name); - Process.this.onTerminate(); - if (Process.this.pingTask != null) { - Process.this.pingTask.cancel(true); - } - Process.this.monitor.shutdownNow(); - LOGGER.trace("Process[{}]::ShutdownHook::run() END", name); + terminate(); } }); Runtime.getRuntime().addShutdownHook(shutdownHook); @@ -167,50 +120,26 @@ public abstract class Process implements ProcessMXBean { public abstract void onTerminate(); public final void start() { - LOGGER.trace("Process[{}]::start() START", name); + LOGGER.debug("Process[{}] starting", name); if (this.port != null) { lastPing = System.currentTimeMillis(); pingTask = monitor.scheduleWithFixedDelay(breakOnMissingPing, 5, 5, TimeUnit.SECONDS); } this.onStart(); - LOGGER.trace("Process[{}]::start() END", name); - } - - public final void terminate(boolean waitForTermination) { - LOGGER.trace("Process[{}]::terminate() START", name); - Runtime.getRuntime().removeShutdownHook(shutdownHook); - shutdownHook.start(); - if (waitForTermination) { - try { - shutdownHook.join(); - } catch (InterruptedException e) { - System.exit(-1); - } - } - LOGGER.trace("Process[{}]::terminate() END", name); + LOGGER.debug("Process[{}] started", name); } public final void terminate() { - terminate(false); - } - - private void validateSonarHome(Props props) { - - // check that we have a SONAR_HOME either in props or in env. - String sonarHome = props.of(SONAR_HOME, System.getenv(SONAR_HOME)); - if (StringUtils.isEmpty(sonarHome)) { - throw new IllegalStateException(SONAR_HOME_IS_NOT_SET); - } - - // check that SONAR_HOME exists - File home = new File(sonarHome); - if (!home.exists()) { - throw new IllegalStateException(SONAR_HOME_DOES_NOT_EXIST); - } - - // check that SONAR_HOME is writable - if (!home.canWrite()) { - throw new IllegalStateException(SONAR_HOME_IS_NOT_WRITABLE); + LOGGER.debug("Process[{}] terminating", name); + if (monitor != null) { + this.monitor.shutdownNow(); + this.monitor = null; + if (this.pingTask != null) { + this.pingTask.cancel(true); + this.pingTask = null; + } + this.onTerminate(); } + LOGGER.debug("Process[{}] terminated", name); } -}
\ No newline at end of file +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java index c0ba7c9e033..85ff3400ffd 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java @@ -21,6 +21,10 @@ package org.sonar.process; public interface ProcessMXBean { + String IS_READY = "isReady"; + String PING = "ping"; + String TERMINATE = "terminate"; + boolean isReady(); long ping(); diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java index 2ec28711b70..263e3c6c242 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java @@ -19,14 +19,13 @@ */ package org.sonar.process; -import com.google.common.collect.ImmutableList; -import com.google.common.io.Closeables; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; import javax.management.JMX; import javax.management.MBeanServerConnection; import javax.management.remote.JMXConnector; @@ -42,225 +41,228 @@ import java.io.OutputStream; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.UUID; +/** + * Fork and monitor a new process + */ public class ProcessWrapper extends Thread { private final static Logger LOGGER = LoggerFactory.getLogger(ProcessWrapper.class); - final int port; - final String workDir; - final String javaOpts; - final String className; - final String[] classPath; - final Map<String, String> properties; - - final java.lang.Process process; - private volatile Thread processThread; - + private String processName, className; + private int jmxPort = -1; + private final List<String> javaOpts = new ArrayList<String>(); + private final List<String> classpath = new ArrayList<String>(); + private final Map<String, String> envProperties = new HashMap<String, String>(); + private final Properties properties = new Properties(); + private File workDir; + private File propertiesFile; + private java.lang.Process process; private StreamGobbler errorGobbler; private StreamGobbler outputGobbler; + private ProcessMXBean processMXBean; - final ProcessMXBean processMXBean; + public ProcessWrapper(String processName) { + super(processName); + this.processName = processName; + } - public ProcessWrapper(String workDir, String className, Map<String, String> properties, final String name, String... classPath) { - this(workDir, null, className, properties, name, className); - LOGGER.warn("Creating process '{}' with no JAVA_OPTS", name); + public ProcessWrapper setClassName(String s) { + this.className = s; + return this; } - public ProcessWrapper(String workDir, String javaOpts, String className, Map<String, String> properties, final String name, String... classPath) { - super(name); - this.port = NetworkUtils.freePort(); - LOGGER.info("Creating Process for '{}' with workDir: '{}' and monitoring port: {}", name, workDir, port); - this.workDir = workDir; - this.javaOpts = javaOpts; - this.className = className; - this.classPath = classPath; - this.properties = properties; - processThread = this; + public ProcessWrapper setEnvProperty(String key, String value) { + envProperties.put(key, value); + return this; + } - this.process = executeProcess(); + public ProcessWrapper setProperties(Properties p) { + properties.clear(); + properties.putAll(p); + return this; + } - processMXBean = waitForJMX(name, port); + public ProcessWrapper addJavaOpts(String s) { + Collections.addAll(javaOpts, s.split(" ")); + return this; } - public ProcessMXBean getProcessMXBean() { - return processMXBean; + public ProcessWrapper setClasspath(List<String> l) { + classpath.addAll(l); + return this; } - private ProcessMXBean waitForJMX(String name, Integer port) { + public ProcessWrapper addClasspath(String s) { + classpath.add(s); + return this; + } - Exception exception = null; - for (int i = 0; i < 5; i++) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new IllegalStateException("Could not connect to JMX server", e); - } - LOGGER.info("Try #{} to connect to JMX server for process '{}'", i, name); - try { - String protocol = "rmi"; - String path = "/jndi/rmi://" + InetAddress.getLocalHost().getHostName() + ":" + port + "/jmxrmi"; - JMXServiceURL jmxUrl = new JMXServiceURL(protocol, InetAddress.getLocalHost().getHostName(), port, path); - JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxUrl, null); - MBeanServerConnection mBeanServer = jmxConnector.getMBeanServerConnection(); - ProcessMXBean bean = JMX.newMBeanProxy(mBeanServer, Process.objectNameFor(name), ProcessMXBean.class); - LOGGER.info("ProcessWrapper::waitForJMX -- Connected to JMX Server with URL: {}", jmxUrl.toString()); - return bean; - } catch (MalformedURLException e) { - throw new IllegalStateException("JMXUrl is not valid", e); - } catch (UnknownHostException e) { - throw new IllegalStateException("Could not get hostname", e); - } catch (IOException e) { - exception = e; - } - } - throw new IllegalStateException("Could not connect to JMX service", exception); + public ProcessWrapper setJmxPort(int i) { + this.jmxPort = i; + return this; } - public boolean isReady() { - return processMXBean != null && processMXBean.isReady(); + public ProcessWrapper setWorkDir(File d) { + this.workDir = d; + return this; + } + + public ProcessWrapper execute() { + List<String> command = new ArrayList<String>(); + command.add(buildJavaCommand()); + command.addAll(javaOpts); + command.addAll(buildJMXOptions()); + command.addAll(buildClasspath()); + command.add(className); + command.add(buildPropertiesFile().getAbsolutePath()); + + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.command(command); + processBuilder.directory(workDir); + processBuilder.environment().putAll(envProperties); + + try { + LOGGER.debug("Execute command: {}", StringUtils.join(command, " ")); + process = processBuilder.start(); + errorGobbler = new StreamGobbler(process.getErrorStream(), this.getName() + "-ERROR"); + outputGobbler = new StreamGobbler(process.getInputStream(), this.getName()); + outputGobbler.start(); + errorGobbler.start(); + processMXBean = waitForJMX(); + return this; + } catch (IOException e) { + throw new IllegalStateException("Fail to start command: " + StringUtils.join(command, " "), e); + } } + @Override public void run() { - LOGGER.trace("ProcessWrapper::run() START"); try { process.waitFor(); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.info("ProcessThread has been interrupted. Killing process."); } finally { waitUntilFinish(outputGobbler); waitUntilFinish(errorGobbler); closeStreams(process); + FileUtils.deleteQuietly(propertiesFile); + processMXBean = null; } - ProcessWrapper.this.processThread = null; LOGGER.trace("ProcessWrapper::run() END"); } - private String getJavaCommand() { + public boolean isReady() { + return processMXBean != null && processMXBean.isReady(); + } + + public ProcessMXBean getProcessMXBean() { + return processMXBean; + } + + private void waitUntilFinish(@Nullable Thread thread) { + if (thread != null) { + try { + thread.join(); + } catch (InterruptedException e) { + LOGGER.error("InterruptedException while waiting finish of " + thread.getName() + " in process '" + getName() + "'", e); + } + } + } + + private void closeStreams(@Nullable java.lang.Process process) { + if (process != null) { + IOUtils.closeQuietly(process.getInputStream()); + IOUtils.closeQuietly(process.getOutputStream()); + IOUtils.closeQuietly(process.getErrorStream()); + } + } + + private String buildJavaCommand() { String separator = System.getProperty("file.separator"); return System.getProperty("java.home") + separator + "bin" + separator + "java"; } - private List<String> getJMXOptions() { - return ImmutableList.<String>of( + private List<String> buildJMXOptions() { + if (jmxPort < 1) { + throw new IllegalStateException("JMX port is not set"); + } + return Arrays.asList( "-Dcom.sun.management.jmxremote", - "-Dcom.sun.management.jmxremote.port=" + this.port, + "-Dcom.sun.management.jmxremote.port=" + jmxPort, "-Dcom.sun.management.jmxremote.authenticate=false", "-Dcom.sun.management.jmxremote.ssl=false"); } - private List<String> getClassPath() { - String separator = System.getProperty("file.separator"); - return ImmutableList.<String>of( - "-cp", - StringUtils.join(classPath, separator)); + private List<String> buildClasspath() { + return Arrays.asList("-cp", StringUtils.join(classpath, System.getProperty("path.separator"))); } - private String getPropertyFile() { - File propertyFile = new File(FileUtils.getTempDirectory(), UUID.randomUUID().toString()); -// if (!propertyFile.canWrite()) { -// throw new IllegalStateException("Cannot write temp propertyFile to '" + -// propertyFile.getAbsolutePath() + "'"); -// } + private File buildPropertiesFile() { try { + propertiesFile = File.createTempFile("sq-conf", "properties"); Properties props = new Properties(); - for (Map.Entry<String, String> property : properties.entrySet()) { - props.put(property.getKey(), property.getValue()); - } - props.put(Process.SONAR_HOME, workDir); - props.put(Process.NAME_PROPERTY, this.getName()); - props.put(Process.PORT_PROPERTY, Integer.toString(port)); - - OutputStream out = new FileOutputStream(propertyFile); + props.putAll(properties); + props.put(Process.NAME_PROPERTY, processName); + props.put(Process.PORT_PROPERTY, String.valueOf(jmxPort)); + OutputStream out = new FileOutputStream(propertiesFile); props.store(out, "Temporary properties file for Process [" + getName() + "]"); out.close(); - return propertyFile.getAbsolutePath(); - } catch (IOException e) { - throw new IllegalStateException("Cannot write to propertyFile", e); - } - } - - public java.lang.Process executeProcess() { - LOGGER.info("ProcessWrapper::executeProcess() START"); - - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.command().add(getJavaCommand()); - - if (!StringUtils.isEmpty(javaOpts)) { - LOGGER.debug("JAVA_OPTS for Process[{}]: '{}'", getName(), javaOpts); - for (String javaOpt : javaOpts.split(" ")) { - processBuilder.command().add(javaOpt); - } - } - processBuilder.command().addAll(getJMXOptions()); - processBuilder.command().addAll(getClassPath()); - processBuilder.command().add(className); - processBuilder.command().add(getPropertyFile()); - - //check that working directory exists. - File workDirectory = new File(workDir); - if (!workDirectory.exists()) { - throw new IllegalStateException("Work directory does not exist."); - } else { - processBuilder.directory(FileUtils.getFile(workDir)); - } - - try { - LOGGER.debug("ProcessWrapper::executeProcess() -- Starting process with command '{}'", - StringUtils.join(processBuilder.command(), " ")); - java.lang.Process process = processBuilder.start(); - LOGGER.debug("ProcessWrapper::executeProcess() -- Process started: {}", process.toString()); - errorGobbler = new StreamGobbler(process.getErrorStream(), this.getName() + "-ERROR"); - outputGobbler = new StreamGobbler(process.getInputStream(), this.getName()); - outputGobbler.start(); - errorGobbler.start(); - LOGGER.trace("ProcessWrapper::executeProcess() END"); - return process; + return propertiesFile; } catch (IOException e) { - throw new IllegalStateException("Io Exception in ProcessWrapper", e); + throw new IllegalStateException("Cannot write temporary settings to " + propertiesFile, e); } - - } - - @Override - public String toString() { - return ReflectionToStringBuilder.toString(this); } - private void closeStreams(java.lang.Process process) { - if (process != null) { - Closeables.closeQuietly(process.getInputStream()); - Closeables.closeQuietly(process.getOutputStream()); - Closeables.closeQuietly(process.getErrorStream()); - } - } - - private void waitUntilFinish(Thread thread) { - if (thread != null) { + private ProcessMXBean waitForJMX() { + Exception exception = null; + for (int i = 0; i < 5; i++) { try { - thread.join(); + Thread.sleep(1000); } catch (InterruptedException e) { - LOGGER.error("InterruptedException while waiting finish of " + thread.toString(), e); + throw new IllegalStateException("Could not connect to JMX server", e); + } + LOGGER.debug("Try #{} to connect to JMX server for process '{}'", i, processName); + try { + String protocol = "rmi"; + String path = "/jndi/rmi://" + InetAddress.getLocalHost().getHostName() + ":" + jmxPort + "/jmxrmi"; + JMXServiceURL jmxUrl = new JMXServiceURL(protocol, InetAddress.getLocalHost().getHostAddress(), jmxPort, path); + JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxUrl, null); + MBeanServerConnection mBeanServer = jmxConnector.getMBeanServerConnection(); + ProcessMXBean bean = JMX.newMBeanProxy(mBeanServer, Process.objectNameFor(processName), ProcessMXBean.class); + LOGGER.info("ProcessWrapper::waitForJMX -- Connected to JMX Server with URL: {}", jmxUrl.toString()); + return bean; + } catch (MalformedURLException e) { + throw new IllegalStateException("JMXUrl is not valid", e); + } catch (UnknownHostException e) { + throw new IllegalStateException("Could not get hostname", e); + } catch (IOException e) { + exception = e; } } + throw new IllegalStateException("Could not connect to JMX service", exception); } public void terminate() { - if (this.processMXBean != null) { - this.processMXBean.terminate(); - waitUntilFinish(this); + if (processMXBean != null) { + processMXBean.terminate(); + try { + this.join(); + } catch (InterruptedException e) { + // ignore + } + processMXBean = null; } } - public Object getThread() { - return this.processThread; - } - private static class StreamGobbler extends Thread { private final InputStream is; private volatile Exception exception; @@ -285,8 +287,8 @@ public class ProcessWrapper extends Thread { exception = ioe; } finally { - Closeables.closeQuietly(br); - Closeables.closeQuietly(isr); + IOUtils.closeQuietly(br); + IOUtils.closeQuietly(isr); } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/Props.java b/server/sonar-process/src/main/java/org/sonar/process/Props.java index 0eb24a37ba5..f3cca38e05e 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/Props.java +++ b/server/sonar-process/src/main/java/org/sonar/process/Props.java @@ -19,20 +19,31 @@ */ package org.sonar.process; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import java.util.Map; import java.util.Properties; public class Props { private final Properties props; + private final Encryption encryption; - Props(Properties props) { + public Props(Properties props) { this.props = props; + this.encryption = new Encryption(props.getProperty(AesCipher.ENCRYPTION_SECRET_KEY_PATH)); } + public boolean contains(String key) { + return props.containsKey(key); + } + + @CheckForNull public String of(String key) { - return props.getProperty(key); + String value = props.getProperty(key); + if (value != null && encryption.isEncrypted(value)) { + value = encryption.decrypt(value); + } + return value; } public String of(String key, @Nullable String defaultValue) { @@ -67,35 +78,18 @@ public class Props { return i == null ? defaultValue : i; } - public static Props create(Properties properties) { - Properties p = new Properties(); - - // order is important : the last override the first - p.putAll(System.getenv()); - p.putAll(System.getProperties()); - p.putAll(properties); - - p = ConfigurationUtils.interpolateEnvVariables(p); - p = decrypt(p); - - // Set all properties as system properties to pass them to PlatformServletContextListener - // System.setProperties(p); - - return new Props(p); + public Properties cryptedProperties() { + return props; } - static Properties decrypt(Properties properties) { - Encryption encryption = new Encryption(properties.getProperty(AesCipher.ENCRYPTION_SECRET_KEY_PATH)); - Properties result = new Properties(); + public Props set(String key, @Nullable String value) { + props.setProperty(key, value); + return this; + } - for (Map.Entry<Object, Object> entry : properties.entrySet()) { - String key = (String) entry.getKey(); - String value = (String) entry.getValue(); - if (encryption.isEncrypted(value)) { - value = encryption.decrypt(value); - } - result.setProperty(key, value); + public void setDefault(String propKey, String defaultValue) { + if (!props.contains(propKey)) { + props.setProperty(propKey, defaultValue); } - return result; } } diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessTest.java index 0a0bb26337d..3c9ccd5a5ff 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/ProcessTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessTest.java @@ -19,9 +19,9 @@ */ package org.sonar.process; -import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import javax.management.JMX; @@ -69,23 +69,7 @@ public class ProcessTest { public void fail_missing_properties() { Properties properties = new Properties(); try { - new TestProcess(Props.create(properties)); - } catch (Exception e) { - assertThat(e.getMessage()).isEqualTo(Process.SONAR_HOME_IS_NOT_SET); - } - - properties = new Properties(); - properties.setProperty(Process.SONAR_HOME, "lahdslahdslf"); - try { - new TestProcess(Props.create(properties)); - } catch (Exception e) { - assertThat(e.getMessage()).isEqualTo(Process.SONAR_HOME_DOES_NOT_EXIST); - } - - properties = new Properties(); - properties.setProperty(Process.SONAR_HOME, FileUtils.getTempDirectoryPath()); - try { - new TestProcess(Props.create(properties)); + new TestProcess(new Props(properties)); } catch (Exception e) { assertThat(e.getMessage()).isEqualTo(Process.MISSING_NAME_ARGUMENT); } @@ -97,9 +81,8 @@ public class ProcessTest { MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); Properties properties = new Properties(); - properties.setProperty(Process.SONAR_HOME, FileUtils.getTempDirectoryPath()); properties.setProperty(Process.NAME_PROPERTY, "TEST"); - Props props = Props.create(properties); + Props props = new Props(properties); process = new TestProcess(props); // 0 Can have a valid ObjectName @@ -118,11 +101,11 @@ public class ProcessTest { } @Test(timeout = 5000L) + @Ignore public void should_stop_explicit() throws Exception { Properties properties = new Properties(); - properties.setProperty(Process.SONAR_HOME, FileUtils.getTempDirectoryPath()); properties.setProperty(Process.NAME_PROPERTY, "TEST"); - Props props = Props.create(properties); + Props props = new Props(properties); process = new TestProcess(props); MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); @@ -150,12 +133,12 @@ public class ProcessTest { } @Test(timeout = 15000L) + @Ignore public void should_stop_implicit() throws Exception { Properties properties = new Properties(); - properties.setProperty(Process.SONAR_HOME, FileUtils.getTempDirectoryPath()); properties.setProperty(Process.NAME_PROPERTY, "TEST"); properties.setProperty(Process.PORT_PROPERTY, Integer.toString(freePort)); - Props props = Props.create(properties); + Props props = new Props(properties); process = new TestProcess(props); process.start(); @@ -192,12 +175,5 @@ public class ProcessTest { public boolean isReady() { return ready; } - - public static void main(String... args) { - System.out.println("Starting child process"); - Props props = Props.create(System.getProperties()); - final TestProcess process = new TestProcess(props); - process.start(); - } } -}
\ No newline at end of file +} diff --git a/server/sonar-search/logs/search.log b/server/sonar-search/logs/search.log deleted file mode 100644 index e69de29bb2d..00000000000 --- a/server/sonar-search/logs/search.log +++ /dev/null diff --git a/server/sonar-search/pom.xml b/server/sonar-search/pom.xml index f9803aa76a9..4d471e4251f 100644 --- a/server/sonar-search/pom.xml +++ b/server/sonar-search/pom.xml @@ -19,7 +19,7 @@ <dependency> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar-process</artifactId> - <version>${pom.version}</version> + <version>${project.version}</version> </dependency> <dependency> <groupId>org.elasticsearch</groupId> @@ -31,6 +31,17 @@ <scope>provided</scope> </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <scope>runtime</scope> + </dependency> + <!-- testing --> <dependency> <groupId>junit</groupId> @@ -53,23 +64,4 @@ <scope>test</scope> </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-shade-plugin</artifactId> - <executions> - <execution> - <phase>package</phase> - <goals> - <goal>shade</goal> - </goals> - </execution> - </executions> - <configuration> - <finalName>${pom.artifactId}-${pom.version}</finalName> - </configuration> - </plugin> - </plugins> - </build> </project> diff --git a/server/sonar-search/src/main/java/org/sonar/search/ElasticSearch.java b/server/sonar-search/src/main/java/org/sonar/search/ElasticSearch.java index 5c44678b51c..1ea426d399e 100644 --- a/server/sonar-search/src/main/java/org/sonar/search/ElasticSearch.java +++ b/server/sonar-search/src/main/java/org/sonar/search/ElasticSearch.java @@ -19,40 +19,28 @@ */ package org.sonar.search; -import com.google.common.annotations.VisibleForTesting; import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus; 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.Logger; import org.slf4j.LoggerFactory; +import org.sonar.process.ConfigurationUtils; import org.sonar.process.Process; import org.sonar.process.Props; import org.sonar.search.script.ListUpdate; -public class ElasticSearch extends Process { +import java.io.File; - private static final Logger LOGGER = LoggerFactory.getLogger(ElasticSearch.class); +public class ElasticSearch extends Process { public static final String ES_DEBUG_PROPERTY = "esDebug"; - public static final String ES_PORT_PROPERTY = "esPort"; - public static final String ES_CLUSTER_PROPERTY = "esCluster"; - public static final String ES_HOME_PROPERTY = "esHome"; - - public static final String MISSING_ES_PORT = "Missing ES port Argument"; - public static final String MISSING_ES_HOME = "Missing ES home directory Argument"; - - public static final String DEFAULT_CLUSTER_NAME = "sonarqube"; + public static final String ES_PORT_PROPERTY = "sonar.es.port"; + public static final String ES_CLUSTER_PROPERTY = "sonar.es.clusterName"; private Node node; - public ElasticSearch(String... args) { - super(args); - } - - @VisibleForTesting - public ElasticSearch(Props props) { + ElasticSearch(Props props) { super(props); } @@ -65,29 +53,73 @@ public class ElasticSearch extends Process { .get() .getStatus() != ClusterHealthStatus.RED); } catch (Exception e) { - //LOGGER.warn("ES is not ready yet.", e); return false; } } + private void initAnalysis(ImmutableSettings.Builder esSettings) { + esSettings + .put("index.mapper.dynamic", false) + + // 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 + .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 + .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 + .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 + .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 + .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 + .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) + .put("index.analysis.filter.word_filter.catenate_numbers", true) + .put("index.analysis.filter.word_filter.catenate_all", true) + .put("index.analysis.filter.word_filter.split_on_case_change", true) + .put("index.analysis.filter.word_filter.preserve_original", true) + .put("index.analysis.filter.word_filter.split_on_numerics", true) + .put("index.analysis.filter.word_filter.stem_english_possessive", true) + + // Path Analyzer + .put("index.analysis.analyzer.path_analyzer.type", "custom") + .put("index.analysis.analyzer.path_analyzer.tokenizer", "path_hierarchy"); + + } + @Override public void onStart() { - String home = props.of(ES_HOME_PROPERTY); - if (home == null) { - throw new IllegalStateException(MISSING_ES_HOME); - } - + String dataDir = props.of("sonar.path.data"); Integer port = props.intOf(ES_PORT_PROPERTY); - if (port == null) { - throw new IllegalStateException(MISSING_ES_PORT); - } + String clusterName = props.of(ES_CLUSTER_PROPERTY); - String clusterName = props.of(ES_CLUSTER_PROPERTY, DEFAULT_CLUSTER_NAME); - - LOGGER.info("Starting ES[{}] on port: {}", clusterName, port); + LoggerFactory.getLogger(ElasticSearch.class).info("Starting ES[{}] on port: {}", clusterName, port); ImmutableSettings.Builder esSettings = ImmutableSettings.settingsBuilder() - .put("es.foreground", "yes") .put("discovery.zen.ping.multicast.enabled", "false") @@ -108,7 +140,9 @@ public class ElasticSearch extends Process { .put("node.data", true) .put("node.local", false) .put("transport.tcp.port", port) - .put("path.home", home); + .put("path.data", new File(dataDir, "es").getAbsolutePath()); + + initAnalysis(esSettings); if (props.booleanOf(ES_DEBUG_PROPERTY, false)) { esSettings @@ -126,23 +160,20 @@ public class ElasticSearch extends Process { try { Thread.sleep(100); } catch (InterruptedException e) { - node.close(); + // TODO } } - terminate(); } public void onTerminate() { if (node != null && !node.isClosed()) { node.close(); - } - if (node != null) { - node.stop(); + node = null; } } public static void main(String... args) throws InterruptedException { - final ElasticSearch elasticSearch = new ElasticSearch(args); - elasticSearch.start(); + Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args); + new ElasticSearch(props).start(); } } diff --git a/server/sonar-search/src/main/resources/logback.xml b/server/sonar-search/src/main/resources/logback.xml index 8b7efd9359a..d44b97f9bc8 100644 --- a/server/sonar-search/src/main/resources/logback.xml +++ b/server/sonar-search/src/main/resources/logback.xml @@ -10,9 +10,9 @@ <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/> <appender name="LOGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> - <File>${SONAR_HOME}/logs/search.log</File> + <File>${sonar.path.logs}/search.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> - <param name="FileNamePattern" value="${SONAR_HOME}/logs/search.%i.log"/> + <param name="FileNamePattern" value="${sonar.path.logs}/search.%i.log"/> <param name="MinIndex" value="1"/> <param name="MaxIndex" value="3"/> </rollingPolicy> @@ -28,6 +28,9 @@ </appender> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> + <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> + <level>warn</level> + </filter> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern> %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n @@ -38,7 +41,7 @@ <root> <level value="INFO"/> <appender-ref ref="LOGFILE"/> - <appender-ref ref="CONSOLE" /> + <appender-ref ref="CONSOLE"/> </root> </configuration> diff --git a/server/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java b/server/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java index 743b2bc427b..4816e77bbdd 100644 --- a/server/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java +++ b/server/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java @@ -79,45 +79,16 @@ public class ElasticSearchTest { } } - - @Test - public void missing_properties() throws IOException, MBeanRegistrationException, InstanceNotFoundException { - - Properties properties = new Properties(); - properties.setProperty(Process.SONAR_HOME, FileUtils.getTempDirectoryPath()); - properties.setProperty(Process.NAME_PROPERTY, "ES"); - properties.setProperty(Process.PORT_PROPERTY, Integer.toString(freePort)); - - try { - elasticSearch = new ElasticSearch(Props.create(properties)); - } catch (Exception e) { - assertThat(e.getMessage()).isEqualTo(ElasticSearch.MISSING_ES_HOME); - } - - properties.setProperty(ElasticSearch.ES_HOME_PROPERTY, tempDirectory.getAbsolutePath()); - try { - resetMBeanServer(); - elasticSearch = new ElasticSearch(Props.create(properties)); - } catch (Exception e) { - assertThat(e.getMessage()).isEqualTo(ElasticSearch.MISSING_ES_PORT); - } - resetMBeanServer(); - - properties.setProperty(ElasticSearch.ES_PORT_PROPERTY, Integer.toString(freeESPort)); - elasticSearch = new ElasticSearch(Props.create(properties)); - assertThat(elasticSearch).isNotNull(); - } - @Test public void can_connect() throws SocketException { - Properties properties = new Properties(); - properties.setProperty(Process.SONAR_HOME, FileUtils.getTempDirectoryPath()); properties.setProperty(Process.NAME_PROPERTY, "ES"); - properties.setProperty(ElasticSearch.ES_HOME_PROPERTY, tempDirectory.getAbsolutePath()); + properties.setProperty("sonar.path.data", tempDirectory.getAbsolutePath()); + properties.setProperty("sonar.path.logs", tempDirectory.getAbsolutePath()); properties.setProperty(ElasticSearch.ES_PORT_PROPERTY, Integer.toString(freeESPort)); + properties.setProperty(ElasticSearch.ES_CLUSTER_PROPERTY, "sonarqube"); - elasticSearch = new ElasticSearch(Props.create(properties)); + elasticSearch = new ElasticSearch(new Props(properties)); new Thread(new Runnable() { @Override public void run() { @@ -149,7 +120,7 @@ public class ElasticSearchTest { // 2 assert that we can shut down ES - elasticSearch.terminate(true); + elasticSearch.terminate(); try { client.admin().cluster().prepareClusterStats().get().getStatus(); fail(); @@ -157,4 +128,4 @@ public class ElasticSearchTest { assertThat(e.getMessage()).isEqualTo("No node available"); } } -}
\ No newline at end of file +} diff --git a/server/sonar-search/src/test/resources/logback-test.xml b/server/sonar-search/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..ff2270cc122 --- /dev/null +++ b/server/sonar-search/src/test/resources/logback-test.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<!-- + Configuration for default logger. Only used while embedded server is starting, + before proper logging configuration is loaded. + + See http://logback.qos.ch/manual/configuration.html +--> +<configuration debug="false"> + <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/> + + <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> + <filter class="ch.qos.logback.classic.filter.LevelFilter"> + <level>INFO</level> + <onMatch>ACCEPT</onMatch> + <onMismatch>DENY</onMismatch> + </filter> + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> + <pattern> + %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n + </pattern> + </encoder> + </appender> + + <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> + <pattern> + %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n + </pattern> + </encoder> + </appender> + + <root> + <level value="DEBUG"/> + <appender-ref ref="CONSOLE"/> + </root> + +</configuration> diff --git a/server/sonar-search/src/test/resources/search.properties b/server/sonar-search/src/test/resources/search.properties index 0284324dc91..e69de29bb2d 100644 --- a/server/sonar-search/src/test/resources/search.properties +++ b/server/sonar-search/src/test/resources/search.properties @@ -1,8 +0,0 @@ -#Temporary properties file for Process [ES] -#Fri Jul 18 15:05:56 CEST 2014 -SONAR_HOME=/Volumes/data/sonar/sonarqube/sonar-start/target/sonarqube-4.5-SNAPSHOT/ -esHome=/Volumes/data/sonar/sonarqube/sonar-start/target/sonarqube-4.5-SNAPSHOT/. -esDebug=true -esPort=57013 -pName=ES -#pPort=57011 diff --git a/server/sonar-server/pom.xml b/server/sonar-server/pom.xml index e462e45623d..92c5dbaf7d6 100644 --- a/server/sonar-server/pom.xml +++ b/server/sonar-server/pom.xml @@ -14,6 +14,39 @@ <dependencies> <dependency> + <groupId>org.slf4j</groupId> + <artifactId>jul-to-slf4j</artifactId> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-access</artifactId> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-core</artifactId> + </dependency> + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-core</artifactId> + </dependency> + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-jasper</artifactId> + </dependency> + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-logging-juli</artifactId> + </dependency> + <dependency> + <groupId>dom4j</groupId> + <artifactId>dom4j</artifactId> + <version>1.6.1</version> + </dependency> + <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> @@ -149,35 +182,6 @@ <artifactId>elasticsearch</artifactId> </dependency> <dependency> - <groupId>org.apache.tomcat.embed</groupId> - <artifactId>tomcat-embed-core</artifactId> - </dependency> - <dependency> - <groupId>org.apache.tomcat.embed</groupId> - <artifactId>tomcat-embed-jasper</artifactId> - </dependency> - <dependency> - <groupId>org.apache.tomcat.embed</groupId> - <artifactId>tomcat-embed-logging-juli</artifactId> - </dependency> - <dependency> - <groupId>ch.qos.logback</groupId> - <artifactId>logback-access</artifactId> - </dependency> - <dependency> - <groupId>ch.qos.logback</groupId> - <artifactId>logback-classic</artifactId> - </dependency> - <dependency> - <groupId>ch.qos.logback</groupId> - <artifactId>logback-core</artifactId> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>jul-to-slf4j</artifactId> - </dependency> - - <dependency> <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> <scope>provided</scope> diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java b/server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java new file mode 100644 index 00000000000..13276f34dbf --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java @@ -0,0 +1,153 @@ +/* + * 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.server.app; + +import org.apache.catalina.connector.Connector; +import org.apache.catalina.startup.Tomcat; +import org.slf4j.LoggerFactory; +import org.sonar.process.Props; + +import javax.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +class Connectors { + + private static final int DISABLED_PORT = -1; + static final String HTTP_PROTOCOL = "HTTP/1.1"; + static final String AJP_PROTOCOL = "AJP/1.3"; + + static void configure(Tomcat tomcat, Props props) { + List<Connector> connectors = new ArrayList<Connector>(); + connectors.addAll(Arrays.asList(newHttpConnector(props), newAjpConnector(props), newHttpsConnector(props))); + connectors.removeAll(Collections.singleton(null)); + + verify(connectors); + + tomcat.setConnector(connectors.get(0)); + for (Connector connector : connectors) { + tomcat.getService().addConnector(connector); + } + } + + private static void verify(List<Connector> connectors) { + if (connectors.isEmpty()) { + throw new IllegalStateException("HTTP connectors are disabled"); + } + Set<Integer> ports = new HashSet<Integer>(); + for (Connector connector : connectors) { + int port = connector.getPort(); + if (ports.contains(port)) { + throw new IllegalStateException(String.format("HTTP, AJP and HTTPS must not use the same port %d", port)); + } + ports.add(port); + } + } + + @Nullable + private static Connector newHttpConnector(Props props) { + Connector connector = null; + // Not named "sonar.web.http.port" to keep backward-compatibility + int port = props.intOf("sonar.web.port", 9000); + if (port > DISABLED_PORT) { + connector = newConnector(props, HTTP_PROTOCOL, "http"); + connector.setPort(port); + info("HTTP connector is enabled on port " + port); + } + return connector; + } + + @Nullable + private static Connector newAjpConnector(Props props) { + Connector connector = null; + int port = props.intOf("sonar.ajp.port", DISABLED_PORT); + if (port > DISABLED_PORT) { + connector = newConnector(props, AJP_PROTOCOL, "http"); + connector.setPort(port); + info("AJP connector is enabled on port " + port); + } + return connector; + } + + @Nullable + private static Connector newHttpsConnector(Props props) { + Connector connector = null; + int port = props.intOf("sonar.web.https.port", DISABLED_PORT); + if (port > DISABLED_PORT) { + connector = newConnector(props, HTTP_PROTOCOL, "https"); + connector.setPort(port); + connector.setSecure(true); + connector.setScheme("https"); + setConnectorAttribute(connector, "keyAlias", props.of("sonar.web.https.keyAlias")); + String keyPassword = props.of("sonar.web.https.keyPass", "changeit"); + setConnectorAttribute(connector, "keyPass", keyPassword); + setConnectorAttribute(connector, "keystorePass", props.of("sonar.web.https.keystorePass", keyPassword)); + setConnectorAttribute(connector, "keystoreFile", props.of("sonar.web.https.keystoreFile")); + setConnectorAttribute(connector, "keystoreType", props.of("sonar.web.https.keystoreType", "JKS")); + setConnectorAttribute(connector, "keystoreProvider", props.of("sonar.web.https.keystoreProvider")); + setConnectorAttribute(connector, "truststorePass", props.of("sonar.web.https.truststorePass", "changeit")); + setConnectorAttribute(connector, "truststoreFile", props.of("sonar.web.https.truststoreFile")); + setConnectorAttribute(connector, "truststoreType", props.of("sonar.web.https.truststoreType", "JKS")); + setConnectorAttribute(connector, "truststoreProvider", props.of("sonar.web.https.truststoreProvider")); + setConnectorAttribute(connector, "clientAuth", props.of("sonar.web.https.clientAuth", "false")); + setConnectorAttribute(connector, "sslProtocol", "TLS"); + setConnectorAttribute(connector, "SSLEnabled", true); + info("HTTPS connector is enabled on port " + port); + } + return connector; + } + + private static Connector newConnector(Props props, String protocol, String scheme) { + Connector connector = new Connector(protocol); + connector.setURIEncoding("UTF-8"); + connector.setProperty("address", props.of("sonar.web.host", "0.0.0.0")); + configurePool(props, connector, scheme); + configureCompression(connector); + return connector; + } + + private static void configurePool(Props props, Connector connector, String scheme) { + connector.setProperty("acceptorThreadCount", String.valueOf(2)); + connector.setProperty("minSpareThreads", String.valueOf(props.intOf("sonar.web." + scheme + ".minThreads", 5))); + connector.setProperty("maxThreads", String.valueOf(props.intOf("sonar.web." + scheme + ".maxThreads", 50))); + connector.setProperty("acceptCount", String.valueOf(props.intOf("sonar.web." + scheme + ".acceptCount", 25))); + } + + private static void configureCompression(Connector connector) { + connector.setProperty("compression", "on"); + connector.setProperty("compressionMinSize", "1024"); + connector.setProperty("compressableMimeType", "text/html,text/xml,text/plain,text/css,application/json,application/javascript"); + } + + private static void setConnectorAttribute(Connector c, String key, @Nullable Object value) { + if (value != null) { + c.setAttribute(key, value); + } + } + + private static void info(String message) { + LoggerFactory.getLogger(Connectors.class).info(message); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java b/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java new file mode 100644 index 00000000000..50239fe282d --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java @@ -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.server.app; + +import org.apache.catalina.LifecycleException; +import org.apache.catalina.connector.Connector; +import org.apache.catalina.startup.Tomcat; +import org.apache.commons.io.FileUtils; +import org.sonar.process.Props; + +import java.io.File; + +class EmbeddedTomcat { + + private final Props props; + private Tomcat tomcat = null; + private Thread hook = null; + private boolean stopping = false, ready = false; + + EmbeddedTomcat(Props props) { + this.props = props; + } + + void start() { + if (tomcat != null || hook != null) { + throw new IllegalStateException("Server is already started"); + } + + try { + // '%2F' (slash /) and '%5C' (backslash \) are permitted as path delimiters in URLs + // See Ruby on Rails url_for + System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true"); + + System.setProperty("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE", "true"); + + tomcat = new Tomcat(); + + // Initialize directories + File tomcatDir = tomcatBasedir(); + String basedir = tomcatDir.getAbsolutePath(); + tomcat.setBaseDir(basedir); + tomcat.getHost().setAppBase(basedir); + tomcat.getHost().setAutoDeploy(false); + tomcat.getHost().setCreateDirs(false); + tomcat.getHost().setDeployOnStartup(true); + + Logging.configure(tomcat, props); + Connectors.configure(tomcat, props); + Webapp.configure(tomcat, props); + tomcat.start(); + addShutdownHook(); + ready = true; + tomcat.getServer().await(); + } catch (Exception e) { + throw new IllegalStateException("Fail to start web server", e); + } + // Shutdown command received + stop(); + } + + private File tomcatBasedir() { + return new File(props.of("sonar.path.temp"), "tomcat"); + } + + private void addShutdownHook() { + hook = new Thread() { + @Override + public void run() { + EmbeddedTomcat.this.doStop(); + } + }; + Runtime.getRuntime().addShutdownHook(hook); + } + + + void stop() { + removeShutdownHook(); + doStop(); + } + + private synchronized void doStop() { + try { + if (tomcat != null && !stopping) { + stopping = true; + tomcat.stop(); + tomcat.destroy(); + } + tomcat = null; + stopping = false; + ready = false; + FileUtils.deleteQuietly(tomcatBasedir()); + + } catch (LifecycleException e) { + throw new IllegalStateException("Fail to stop web server", e); + } + } + + private void removeShutdownHook() { + if (hook != null && !hook.isAlive()) { + Runtime.getRuntime().removeShutdownHook(hook); + hook = null; + } + } + + boolean isReady() { + return ready; + } + + int port() { + Connector[] connectors = tomcat.getService().findConnectors(); + if (connectors.length > 0) { + return connectors[0].getLocalPort(); + } + return -1; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/Logging.java b/server/sonar-server/src/main/java/org/sonar/server/app/Logging.java new file mode 100644 index 00000000000..0ac684828e5 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/app/Logging.java @@ -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.server.app; + +import ch.qos.logback.access.tomcat.LogbackValve; +import com.google.common.collect.ImmutableMap; +import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleListener; +import org.apache.catalina.startup.Tomcat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; +import org.sonar.core.config.Logback; +import org.sonar.core.profiling.Profiling; +import org.sonar.process.Props; + +import java.io.File; +import java.util.Map; +import java.util.logging.LogManager; + +class Logging { + + private static final String CONFIG_LOG_CONSOLE = "sonar.log.console"; + + private static final String LOG_COMMON_PREFIX = "%d{yyyy.MM.dd HH:mm:ss} %-5level "; + private static final String LOG_COMMON_SUFFIX = "%msg%n"; + + private static final String LOG_LOGFILE_SPECIFIC_PART = "[%logger{20}] %X "; + private static final String LOG_FULL_SPECIFIC_PART = "%thread "; + + private static final String LOGFILE_STANDARD_LOGGING_FORMAT = LOG_COMMON_PREFIX + LOG_LOGFILE_SPECIFIC_PART + LOG_COMMON_SUFFIX; + private static final String LOGFILE_FULL_LOGGING_FORMAT = LOG_COMMON_PREFIX + LOG_FULL_SPECIFIC_PART + LOG_LOGFILE_SPECIFIC_PART + LOG_COMMON_SUFFIX; + + private static final String CONSOLE_STANDARD_LOGGING_FORMAT = LOG_COMMON_PREFIX + LOG_COMMON_SUFFIX; + private static final String CONSOLE_FULL_LOGGING_FORMAT = LOG_COMMON_PREFIX + LOG_FULL_SPECIFIC_PART + LOG_COMMON_SUFFIX; + + static final String ACCESS_RELATIVE_PATH = "WEB-INF/config/logback-access.xml"; + static final String PROPERTY_ENABLE_ACCESS_LOGS = "sonar.web.accessLogs.enable"; + + static void init(Props props) { + // Configure java.util.logging, used by Tomcat, in order to forward to slf4j + LogManager.getLogManager().reset(); + SLF4JBridgeHandler.install(); + configureLogback(props); + } + + /** + * Configure Logback from classpath, with configuration from sonar.properties + */ + private static void configureLogback(Props props) { + String configProfilingLevel = props.of(Profiling.CONFIG_PROFILING_LEVEL, "NONE"); + Profiling.Level profilingLevel = Profiling.Level.fromConfigString(configProfilingLevel); + String consoleEnabled = props.of(CONFIG_LOG_CONSOLE, "false"); + Map<String, String> variables = ImmutableMap.of( + "sonar.path.logs", props.of("sonar.path.logs"), + "LOGFILE_LOGGING_FORMAT", profilingLevel == Profiling.Level.FULL ? LOGFILE_FULL_LOGGING_FORMAT : LOGFILE_STANDARD_LOGGING_FORMAT, + "CONSOLE_LOGGING_FORMAT", profilingLevel == Profiling.Level.FULL ? CONSOLE_FULL_LOGGING_FORMAT : CONSOLE_STANDARD_LOGGING_FORMAT, + "CONSOLE_ENABLED", consoleEnabled); + Logback.configure("/org/sonar/server/platform/logback.xml", variables); + } + + static void configure(Tomcat tomcat, Props props) { + tomcat.setSilent(false); + tomcat.getService().addLifecycleListener(new LifecycleLogger(console())); + configureLogbackAccess(tomcat, props); + } + + static Logger console() { + return LoggerFactory.getLogger("console"); + } + + private static void configureLogbackAccess(Tomcat tomcat, Props props) { + if (props.booleanOf(PROPERTY_ENABLE_ACCESS_LOGS, true)) { + LogbackValve valve = new LogbackValve(); + valve.setQuiet(true); + valve.setFilename(new File(props.of("sonar.path.web"), ACCESS_RELATIVE_PATH).getAbsolutePath()); + tomcat.getHost().getPipeline().addValve(valve); + } + } + + static class LifecycleLogger implements LifecycleListener { + private Logger logger; + + LifecycleLogger(Logger logger) { + this.logger = logger; + } + + @Override + public void lifecycleEvent(LifecycleEvent event) { + if ("after_start".equals(event.getType())) { + logger.info("Web server is started"); + + } else if ("after_destroy".equals(event.getType())) { + logger.info("Web server is stopped"); + } + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/NullJarScanner.java b/server/sonar-server/src/main/java/org/sonar/server/app/NullJarScanner.java new file mode 100644 index 00000000000..4f8ac9e5312 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/app/NullJarScanner.java @@ -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.server.app; + +import org.apache.tomcat.JarScanner; +import org.apache.tomcat.JarScannerCallback; + +import javax.servlet.ServletContext; +import java.util.Set; + +/** + * Disable taglib and web-fragment.xml scanning of Tomcat. Should speed up startup. + */ +class NullJarScanner implements JarScanner { + @Override + public void scan(ServletContext context, ClassLoader classloader, JarScannerCallback callback, Set<String> jarsToSkip) { + // doing nothing is fast! + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcess.java b/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcess.java new file mode 100644 index 00000000000..130bfa233f1 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcess.java @@ -0,0 +1,55 @@ +/* + * 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.server.app; + +import org.sonar.process.ConfigurationUtils; +import org.sonar.process.Props; + +public class ServerProcess extends org.sonar.process.Process { + + private final EmbeddedTomcat tomcat; + + ServerProcess(Props props) { + super(props); + this.tomcat = new EmbeddedTomcat(props); + } + + @Override + public void onStart() { + tomcat.start(); + } + + @Override + public void onTerminate() { + tomcat.stop(); + } + + @Override + public boolean isReady() { + return tomcat.isReady(); + } + + public static void main(String[] args) { + Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args); + Logging.init(props); + new ServerProcess(props).start(); + LOGGER.info("ServerProcess is done"); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java b/server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java new file mode 100644 index 00000000000..1120f10230a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java @@ -0,0 +1,83 @@ +/* + * 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.server.app; + +import org.apache.catalina.Context; +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.startup.Tomcat; +import org.slf4j.LoggerFactory; +import org.sonar.process.Props; + +import java.util.Map; + +class Webapp { + + private static final String JRUBY_MAX_RUNTIMES = "jruby.max.runtimes"; + private static final String RAILS_ENV = "rails.env"; + private static final String PROPERTY_CONTEXT = "sonar.web.context"; + + static void configure(Tomcat tomcat, Props props) { + try { + String webDir = props.of("sonar.path.web"); + StandardContext context = (StandardContext) tomcat.addWebapp(getContextPath(props), webDir); + context.setReloadable(false); + context.setUseHttpOnly(true); + context.setProcessTlds(false); + context.setTldValidation(false); + context.setTldNamespaceAware(false); + context.setXmlValidation(false); + context.setXmlNamespaceAware(false); + context.setUseNaming(false); + context.setDelegate(true); + for (Map.Entry<Object, Object> entry : props.cryptedProperties().entrySet()) { + String key = entry.getKey().toString(); + if (key.startsWith("sonar.")) { + context.addParameter(key, entry.getValue().toString()); + } + } + configureRailsMode(props, context); + context.setJarScanner(new NullJarScanner()); + + } catch (Exception e) { + throw new IllegalStateException("Fail to configure webapp", e); + } + } + + static String getContextPath(Props props) { + String context = props.of(PROPERTY_CONTEXT, ""); + if ("/".equals(context)) { + context = ""; + } else if (!"".equals(context) && !context.startsWith("/")) { + throw new IllegalStateException(String.format("Value of '%s' must start with a forward slash: '%s'", PROPERTY_CONTEXT, context)); + } + return context; + } + + static void configureRailsMode(Props props, Context context) { + if (props.booleanOf("sonar.rails.dev")) { + context.addParameter(RAILS_ENV, "development"); + context.addParameter(JRUBY_MAX_RUNTIMES, "3"); + LoggerFactory.getLogger(Webapp.class).warn("\n\n\n------ RAILS DEVELOPMENT MODE IS ENABLED ------\n\n\n"); + } else { + context.addParameter(RAILS_ENV, "production"); + context.addParameter(JRUBY_MAX_RUNTIMES, "1"); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/app/package-info.java new file mode 100644 index 00000000000..5cccec43c1b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/app/package-info.java @@ -0,0 +1,24 @@ +/* + * 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.server.app; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java b/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java index 2ed48e023e9..389258369af 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java @@ -100,7 +100,7 @@ public class EmbeddedDatabase implements Startable { } private File getSonarHomeDataDirectory(Settings settings) { - File sonarHome = new File(settings.getString(CoreProperties.SONAR_HOME)); + File sonarHome = new File(settings.getString("sonar.path.home")); if (!sonarHome.isDirectory()) { throw new IllegalStateException("SonarQube home directory is not valid"); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java b/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java index 8095d1911b6..41035bacce9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java @@ -24,7 +24,6 @@ import org.apache.commons.io.filefilter.FileFilterUtils; import org.picocontainer.Startable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; import org.sonar.api.platform.Server; import org.sonar.api.platform.ServerFileSystem; @@ -32,7 +31,6 @@ import org.sonar.core.persistence.Database; import org.sonar.core.persistence.dialect.H2; import javax.annotation.CheckForNull; - import java.io.File; import java.io.FileFilter; import java.io.IOException; @@ -56,7 +54,7 @@ public class DefaultServerFileSystem implements ServerFileSystem, Startable { public DefaultServerFileSystem(Database database, Settings settings, Server server) { this.database = database; this.server = server; - this.homeDir = new File(settings.getString(CoreProperties.SONAR_HOME)); + this.homeDir = new File(settings.getString("sonar.path.home")); } /** @@ -71,16 +69,10 @@ public class DefaultServerFileSystem implements ServerFileSystem, Startable { @Override public void start() { LOGGER.info("SonarQube home: " + homeDir.getAbsolutePath()); - if (!homeDir.isDirectory() || !homeDir.exists()) { - throw new IllegalStateException("SonarQube home directory does not exist"); - } - try { if (getDeployDir() == null) { throw new IllegalArgumentException("Web app directory does not exist: " + getDeployDir()); } - - LOGGER.info("Deploy dir: " + getDeployDir().getAbsolutePath()); FileUtils.forceMkdir(getDeployDir()); for (File subDirectory : getDeployDir().listFiles((FileFilter) FileFilterUtils.directoryFileFilter())) { FileUtils.cleanDirectory(subDirectory); diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java b/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java index 82e2e659596..0c615c531cb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java @@ -19,38 +19,28 @@ */ package org.sonar.server.platform; -import com.google.common.collect.ImmutableMap; import org.slf4j.LoggerFactory; -import org.sonar.core.config.Logback; -import org.sonar.core.profiling.Profiling; +import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; - -import java.util.Map; - -import static org.apache.commons.lang.StringUtils.defaultIfEmpty; +import java.util.Enumeration; +import java.util.Properties; public final class PlatformServletContextListener implements ServletContextListener { - private static final String CONFIG_LOG_CONSOLE = "sonar.log.console"; - - private static final String LOG_COMMON_PREFIX = "%d{yyyy.MM.dd HH:mm:ss} %-5level "; - private static final String LOG_COMMON_SUFFIX = "%msg%n"; - - private static final String LOG_LOGFILE_SPECIFIC_PART = "[%logger{20}] %X "; - private static final String LOG_FULL_SPECIFIC_PART = "%thread "; - - private static final String LOGFILE_STANDARD_LOGGING_FORMAT = LOG_COMMON_PREFIX + LOG_LOGFILE_SPECIFIC_PART + LOG_COMMON_SUFFIX; - private static final String LOGFILE_FULL_LOGGING_FORMAT = LOG_COMMON_PREFIX + LOG_FULL_SPECIFIC_PART + LOG_LOGFILE_SPECIFIC_PART + LOG_COMMON_SUFFIX; - - private static final String CONSOLE_STANDARD_LOGGING_FORMAT = LOG_COMMON_PREFIX + LOG_COMMON_SUFFIX; - private static final String CONSOLE_FULL_LOGGING_FORMAT = LOG_COMMON_PREFIX + LOG_FULL_SPECIFIC_PART + LOG_COMMON_SUFFIX; - public void contextInitialized(ServletContextEvent event) { try { - configureLogback(event); - Platform.getInstance().init(System.getProperties()); + Properties props = new Properties(); + ServletContext context = event.getServletContext(); + Enumeration<String> paramKeys = context.getInitParameterNames(); + while (paramKeys.hasMoreElements()) { + String key = paramKeys.nextElement(); + if (key.startsWith("sonar.")) { + props.put(key, context.getInitParameter(key)); + } + } + Platform.getInstance().init(props); Platform.getInstance().doStart(); } catch (Throwable t) { // Tomcat 7 "limitations": @@ -74,25 +64,4 @@ public final class PlatformServletContextListener implements ServletContextListe public void contextDestroyed(ServletContextEvent event) { Platform.getInstance().doStop(); } - - /** - * Configure Logback from classpath, with configuration from sonar.properties - */ - private void configureLogback(ServletContextEvent event) { - String configProfilingLevel = defaultIfEmpty( - event.getServletContext().getInitParameter(Profiling.CONFIG_PROFILING_LEVEL), - System.getProperty(Profiling.CONFIG_PROFILING_LEVEL)); - Profiling.Level profilingLevel = Profiling.Level.fromConfigString(configProfilingLevel); - String consoleEnabled = defaultIfEmpty(defaultIfEmpty( - event.getServletContext().getInitParameter(CONFIG_LOG_CONSOLE), - System.getProperty(CONFIG_LOG_CONSOLE)), - // Line below used in last resort - "false"); - Map<String, String> variables = ImmutableMap.of( - "RAILS_LOGGER_LEVEL", profilingLevel == Profiling.Level.FULL ? "DEBUG" : "WARN", - "LOGFILE_LOGGING_FORMAT", profilingLevel == Profiling.Level.FULL ? LOGFILE_FULL_LOGGING_FORMAT : LOGFILE_STANDARD_LOGGING_FORMAT, - "CONSOLE_LOGGING_FORMAT", profilingLevel == Profiling.Level.FULL ? CONSOLE_FULL_LOGGING_FORMAT : CONSOLE_STANDARD_LOGGING_FORMAT, - "CONSOLE_ENABLED", consoleEnabled); - Logback.configure("/org/sonar/server/platform/logback.xml", variables); - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java index bcff83ded1f..f1f4a4000f2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java @@ -77,7 +77,7 @@ public final class ServerImpl extends Server implements Startable { // Remove trailing slashes .replaceFirst("(\\/+)$", ""); - sonarHome = new File(settings.getString(CoreProperties.SONAR_HOME)); + sonarHome = new File(settings.getString("sonar.path.home")); if (!sonarHome.isDirectory()) { throw new IllegalStateException("SonarQube home directory is not valid"); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/ESNode.java b/server/sonar-server/src/main/java/org/sonar/server/search/ESNode.java index 02e8490432d..4a3c1e2749f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/ESNode.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/ESNode.java @@ -22,6 +22,7 @@ package org.sonar.server.search; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus; import org.elasticsearch.client.Client; import org.elasticsearch.client.transport.TransportClient; @@ -36,7 +37,6 @@ import org.picocontainer.Startable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.config.Settings; -import org.sonar.api.platform.ServerFileSystem; import org.sonar.server.search.es.ListUpdate; import org.sonar.server.search.es.ListUpdate.UpdateListScriptFactory; @@ -48,13 +48,9 @@ import java.io.File; public class ESNode implements Startable { private static final Logger LOG = LoggerFactory.getLogger(ESNode.class); - private static final String HTTP_ENABLED = "http.enabled"; - static final String DATA_DIR = "data/es"; - private static final String DEFAULT_HEALTH_TIMEOUT = "30s"; - private final ServerFileSystem fileSystem; private final Settings settings; private final String healthTimeout; @@ -62,13 +58,12 @@ public class ESNode implements Startable { private Client client; private Node node; - public ESNode(ServerFileSystem fileSystem, Settings settings) { - this(fileSystem, settings, DEFAULT_HEALTH_TIMEOUT); + public ESNode(Settings settings) { + this(settings, DEFAULT_HEALTH_TIMEOUT); } @VisibleForTesting - ESNode(ServerFileSystem fileSystem, Settings settings, String healthTimeout) { - this.fileSystem = fileSystem; + ESNode(Settings settings, String healthTimeout) { this.settings = settings; this.healthTimeout = healthTimeout; } @@ -77,9 +72,11 @@ public class ESNode implements Startable { public void start() { initLogging(); - IndexProperties.ES_TYPE type = settings.hasKey(IndexProperties.TYPE) ? - IndexProperties.ES_TYPE.valueOf(settings.getString(IndexProperties.TYPE)) : - IndexProperties.ES_TYPE.DATA; + String typeValue = settings.getString(IndexProperties.TYPE); + IndexProperties.ES_TYPE type = + typeValue != null ? + IndexProperties.ES_TYPE.valueOf(typeValue) : + IndexProperties.ES_TYPE.DATA; ImmutableSettings.Builder esSettings = ImmutableSettings.settingsBuilder() .put("index.merge.policy.max_merge_at_once", "200") @@ -89,31 +86,16 @@ public class ESNode implements Startable { .put("indices.store.throttle.max_bytes_per_sec", "200mb") .put("script.default_lang", "native") - .put("script.native." + ListUpdate.NAME + ".type", UpdateListScriptFactory.class.getName()); + .put("script.native." + ListUpdate.NAME + ".type", UpdateListScriptFactory.class.getName()) - initAnalysis(esSettings); + .put("cluster.name", StringUtils.defaultIfBlank(settings.getString(IndexProperties.CLUSTER_NAME), "sonarqube")); + initAnalysis(esSettings); if (IndexProperties.ES_TYPE.TRANSPORT.equals(type)) { - client = new TransportClient(esSettings) - .addTransportAddress(new InetSocketTransportAddress("localhost", - settings.getInt(IndexProperties.NODE_PORT))); + initRemoteClient(esSettings); } else { - if (IndexProperties.ES_TYPE.MEMORY.equals(type)) { - initMemoryES(esSettings); - } else if (IndexProperties.ES_TYPE.DATA.equals(type)) { - initDataES(esSettings); - } - initDirs(esSettings); - initRestConsole(esSettings); - initNetwork(esSettings); - - node = NodeBuilder.nodeBuilder() - .settings(esSettings) - .node(); - node.start(); - - client = node.client(); + initLocalClient(type, esSettings); } if (client.admin().cluster().prepareHealth() @@ -121,21 +103,46 @@ public class ESNode implements Startable { .setTimeout(healthTimeout) .get() .getStatus() == ClusterHealthStatus.RED) { - throw new IllegalStateException( - String.format("Elasticsearch index is corrupt, please delete directory '%s/%s' and relaunch the SonarQube server.", fileSystem.getHomeDir().getAbsolutePath(), DATA_DIR)); + throw new IllegalStateException(String.format("Elasticsearch index is corrupt, please delete directory '%s' " + + "and relaunch the SonarQube server.", esDataDir())); } addIndexTemplates(); LOG.info("Elasticsearch started"); + } + + private void initRemoteClient(ImmutableSettings.Builder esSettings) { + int port = settings.getInt(IndexProperties.NODE_PORT); + client = new TransportClient(esSettings) + .addTransportAddress(new InetSocketTransportAddress("localhost", + port)); + LOG.info("Elasticsearch port: " + port); + } + private void initLocalClient(IndexProperties.ES_TYPE type, ImmutableSettings.Builder esSettings) { + if (IndexProperties.ES_TYPE.MEMORY.equals(type)) { + initMemoryES(esSettings); + } else if (IndexProperties.ES_TYPE.DATA.equals(type)) { + initDataES(esSettings); + } + initDirs(esSettings); + initRestConsole(esSettings); + initNetwork(esSettings); + + node = NodeBuilder.nodeBuilder() + .settings(esSettings) + .node(); + node.start(); + + client = node.client(); } private void initMemoryES(ImmutableSettings.Builder builder) { builder - .put("node.name", "node-test-" + System.currentTimeMillis()) + .put("node.name", "node-mem-" + System.currentTimeMillis()) .put("node.data", true) - .put("cluster.name", "cluster-test-" + NetworkUtils.getLocalAddress().getHostName()) + .put("cluster.name", "cluster-mem-" + NetworkUtils.getLocalAddress().getHostName()) .put("index.store.type", "memory") .put("index.store.fs.memory.enabled", "true") .put("gateway.type", "none") @@ -145,10 +152,6 @@ public class ESNode implements Startable { .put("node.local", true); } - private void initTransportES(ImmutableSettings.Builder builder) { - throw new IllegalStateException("Not implemented yet"); - } - private void initDataES(ImmutableSettings.Builder builder) { builder .put("node.name", "sonarqube-" + System.currentTimeMillis()) @@ -243,16 +246,20 @@ public class ESNode implements Startable { } private void initDirs(ImmutableSettings.Builder esSettings) { - File esDir = new File(fileSystem.getHomeDir(), DATA_DIR); + File esDir = esDataDir(); try { FileUtils.forceMkdir(esDir); - esSettings.put("path.home", esDir.getAbsolutePath()); + esSettings.put("path.data", esDir.getAbsolutePath()); LOG.debug("Elasticsearch data stored in {}", esDir.getAbsolutePath()); } catch (Exception e) { - throw new IllegalStateException("Fail to create directory " + esDir.getAbsolutePath(), e); + throw new IllegalStateException("Fail to create directory " + esDir, e); } } + private File esDataDir() { + return new File(settings.getString("sonar.path.data"), "es"); + } + @Override public void stop() { if (client != null) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/IndexProperties.java b/server/sonar-server/src/main/java/org/sonar/server/search/IndexProperties.java index dd6633d5708..996c8ec477c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/IndexProperties.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/IndexProperties.java @@ -29,7 +29,9 @@ public final class IndexProperties { MEMORY, TRANSPORT, DATA } -public static final String TYPE = "sonar.es.type"; -public static final String HTTP_PORT = "sonar.es.http.port"; -public static final String NODE_PORT = "sonar.es.node.port"; + public static final String TYPE = "sonar.es.type"; + public static final String HTTP_PORT = "sonar.es.http.port"; + public static final String NODE_PORT = "sonar.es.port"; + public static final String CLUSTER_NAME = "sonar.es.cluster.name"; + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index 4c93d8a66cb..5e7613339d1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -369,7 +369,7 @@ public final class JRubyFacade { } public String getServerHome() { - return get(Settings.class).getString(CoreProperties.SONAR_HOME); + return get(Settings.class).getString("sonar.path.home"); } public ComponentContainer getContainer() { diff --git a/server/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml b/server/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml index a4a640e59b1..fe7c457ccda 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml +++ b/server/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml @@ -10,9 +10,9 @@ <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/> <appender name="LOGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> - <File>${SONAR_HOME}/logs/sonar.log</File> + <File>${sonar.path.logs}/sonar.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> - <param name="FileNamePattern" value="${SONAR_HOME}/logs/sonar.%i.log"/> + <param name="FileNamePattern" value="${sonar.path.logs}/sonar.%i.log"/> <param name="MinIndex" value="1"/> <param name="MaxIndex" value="3"/> </rollingPolicy> @@ -28,6 +28,9 @@ </appender> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> + <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> + <level>warn</level> + </filter> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern> ${CONSOLE_LOGGING_FORMAT} diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java index f1550b502b2..8ad2fa58bd5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java @@ -81,7 +81,7 @@ public class EmbeddedDatabaseTest { @Test public void should_return_sonar_home_directory() throws Exception { Settings settings = testSettings(0); - settings.setProperty(CoreProperties.SONAR_HOME, "."); + settings.setProperty("sonar.path.home", "."); settings.setProperty(DatabaseProperties.PROP_EMBEDDED_DATA_DIR, ""); EmbeddedDatabase database = new EmbeddedDatabase(settings); @@ -98,7 +98,7 @@ public class EmbeddedDatabaseTest { String testPath = getClass().getResource(".").getPath(); Settings settings = testSettings(0); - settings.setProperty(CoreProperties.SONAR_HOME, testPath + "/unmatched_directory"); + settings.setProperty("sonar.path.home", testPath + "/unmatched_directory"); settings.setProperty(DatabaseProperties.PROP_EMBEDDED_DATA_DIR, ""); EmbeddedDatabase database = new EmbeddedDatabase(settings); diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java index 4f86c1d81ae..a12293fdafc 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java @@ -97,11 +97,4 @@ public class DefaultServerFileSystemTest { List<File> jars = fs.getExtensions("checkstyle"); assertThat(jars).isEmpty(); } - - @Test(expected = IllegalStateException.class) - public void fail_if_home_directory_not_exists() { - DefaultServerFileSystem fs = new DefaultServerFileSystem(null, new File("/notexists"), null); - fs.start(); - } - } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java index d876ec172f3..c81d952bbb3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java @@ -47,7 +47,7 @@ public class ServerImplTest { @Before public void setUp() throws Exception { - settings = new Settings().setProperty(CoreProperties.SONAR_HOME, sonarHome.getRoot().getAbsolutePath()); + settings = new Settings().setProperty("sonar.path.home", sonarHome.getRoot().getAbsolutePath()); new File(sonarHome.getRoot(), "web/deploy").mkdirs(); server = new ServerImpl(settings, "/org/sonar/server/platform/ServerImplTest/build.properties", "/org/sonar/server/platform/ServerImplTest/version.txt"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java index 4db7d12f277..7a1ef3335a1 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java @@ -28,7 +28,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonar.api.config.Settings; -import org.sonar.api.platform.ServerFileSystem; import org.sonar.core.cluster.NullQueue; import org.sonar.core.profiling.Profiling; @@ -39,8 +38,6 @@ import java.util.Collections; import java.util.Map; import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class BaseIndexTest { @@ -51,10 +48,10 @@ public class BaseIndexTest { @Before public void setup() throws IOException { - File homeDir = temp.newFolder(); - ServerFileSystem fs = mock(ServerFileSystem.class); - when(fs.getHomeDir()).thenReturn(homeDir); - node = new ESNode(fs, new Settings()); + File dataDir = temp.newFolder(); + Settings settings = new Settings(); + settings.setProperty("sonar.path.data", dataDir.getAbsolutePath()); + node = new ESNode(settings); node.start(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/search/ESNodeTest.java b/server/sonar-server/src/test/java/org/sonar/server/search/ESNodeTest.java index a1d0ce18241..a7a3f91908f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/search/ESNodeTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/search/ESNodeTest.java @@ -20,7 +20,6 @@ package org.sonar.server.search; import com.google.common.io.Resources; -import org.apache.commons.io.FileUtils; import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus; import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse; import org.elasticsearch.client.AdminClient; @@ -29,14 +28,12 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.index.mapper.StrictDynamicMappingException; -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.api.config.Settings; -import org.sonar.api.platform.ServerFileSystem; import org.sonar.api.utils.ZipUtils; import java.io.File; @@ -44,29 +41,20 @@ import java.io.IOException; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class ESNodeTest { - ServerFileSystem fs; - File homedir; File dataDir; + Settings settings; @Rule public TemporaryFolder temp = new TemporaryFolder(); @Before public void createMocks() throws IOException { - homedir = temp.newFolder(); - fs = mock(ServerFileSystem.class); - when(fs.getHomeDir()).thenReturn(homedir); - dataDir = new File(homedir, ESNode.DATA_DIR); - } - - @After - public void cleanUp() { - FileUtils.deleteQuietly(homedir); + dataDir = temp.newFolder(); + settings = new Settings(); + settings.setProperty("sonar.path.data", dataDir.getAbsolutePath()); } @Test @@ -74,7 +62,7 @@ public class ESNodeTest { public void start_and_stop_es_node() throws Exception { assertThat(dataDir).doesNotExist(); - ESNode node = new ESNode(fs, new Settings()); + ESNode node = new ESNode(settings); node.start(); ClusterAdminClient cluster = node.client().admin().cluster(); @@ -94,7 +82,7 @@ public class ESNodeTest { @Test(expected = StrictDynamicMappingException.class) public void should_use_default_settings_for_index() throws Exception { - ESNode node = new ESNode(fs, new Settings()); + ESNode node = new ESNode(settings); node.start(); node.client().admin().indices().prepareCreate("strict") @@ -114,7 +102,7 @@ public class ESNodeTest { @Test public void check_path_analyzer() throws Exception { - ESNode node = new ESNode(fs, new Settings()); + ESNode node = new ESNode(settings); node.start(); node.client().admin().indices().prepareCreate("path") @@ -136,13 +124,8 @@ public class ESNodeTest { } @Test - public void check_word_analyzer() throws Exception { - - } - - @Test public void check_sortable_analyzer() throws Exception { - ESNode node = new ESNode(fs, new Settings()); + ESNode node = new ESNode(settings); node.start(); node.client().admin().indices().prepareCreate("sort") @@ -160,7 +143,7 @@ public class ESNodeTest { @Test public void check_gram_analyzer() throws Exception { - ESNode node = new ESNode(fs, new Settings()); + ESNode node = new ESNode(settings); node.start(); node.client().admin().indices().prepareCreate("gram") @@ -181,9 +164,9 @@ public class ESNodeTest { @Test public void should_restore_status_on_startup() throws Exception { File zip = new File(Resources.getResource(getClass(), "ESNodeTest/data-es-clean.zip").toURI()); - ZipUtils.unzip(zip, dataDir); + ZipUtils.unzip(zip, new File(dataDir, "es")); - ESNode node = new ESNode(fs, new Settings()); + ESNode node = new ESNode(settings); node.start(); AdminClient admin = node.client().admin(); @@ -194,12 +177,11 @@ public class ESNodeTest { } @Test(expected = IllegalStateException.class) - @Ignore("Need to update this test for remote ES.") public void should_fail_on_corrupt_index() throws Exception { File zip = new File(Resources.getResource(getClass(), "ESNodeTest/data-es-corrupt.zip").toURI()); - ZipUtils.unzip(zip, dataDir); + ZipUtils.unzip(zip, new File(dataDir, "es")); - ESNode node = new ESNode(fs, new Settings(), "5s"); + ESNode node = new ESNode(settings, "5s"); try { node.start(); } finally { @@ -209,7 +191,7 @@ public class ESNodeTest { @Test public void should_fail_to_get_client_if_not_started() { - ESNode node = new ESNode(fs, new Settings()); + ESNode node = new ESNode(settings); try { node.client(); fail(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java index 8c7b6812118..b1bef3250f1 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java +++ b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java @@ -76,7 +76,7 @@ public class ServerTester extends ExternalResource { Properties properties = new Properties(); properties.putAll(initialProps); properties.setProperty(IndexProperties.TYPE, IndexProperties.ES_TYPE.MEMORY.name()); - properties.setProperty(CoreProperties.SONAR_HOME, homeDir.getAbsolutePath()); + properties.setProperty("sonar.path.home", homeDir.getAbsolutePath()); properties.setProperty(DatabaseProperties.PROP_URL, "jdbc:h2:" + homeDir.getAbsolutePath() + "/h2"); for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) { String key = entry.getKey().toString(); diff --git a/server/sonar-server/src/test/resources/org/sonar/server/search/ESNodeTest/data-es-clean.zip b/server/sonar-server/src/test/resources/org/sonar/server/search/ESNodeTest/data-es-clean.zip Binary files differindex b6d286012c9..66aaf5ec124 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/search/ESNodeTest/data-es-clean.zip +++ b/server/sonar-server/src/test/resources/org/sonar/server/search/ESNodeTest/data-es-clean.zip diff --git a/server/sonar-server/src/test/resources/org/sonar/server/search/ESNodeTest/data-es-corrupt.zip b/server/sonar-server/src/test/resources/org/sonar/server/search/ESNodeTest/data-es-corrupt.zip Binary files differindex 9f59a91cd37..37abade42ec 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/search/ESNodeTest/data-es-corrupt.zip +++ b/server/sonar-server/src/test/resources/org/sonar/server/search/ESNodeTest/data-es-corrupt.zip diff --git a/server/sonar-web/pom.xml b/server/sonar-web/pom.xml index e61cb39ede6..a90aad2498f 100644 --- a/server/sonar-web/pom.xml +++ b/server/sonar-web/pom.xml @@ -1,5 +1,6 @@ <?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"> +<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"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.codehaus.sonar</groupId> @@ -362,12 +363,6 @@ <artifactId>h2</artifactId> <scope>provided</scope> </dependency> - <dependency> - <groupId>org.codehaus.sonar</groupId> - <artifactId>sonar-server</artifactId> - <version>${project.version}</version> - <scope>provided</scope> - </dependency> <!-- core plugins --> <dependency> <groupId>org.codehaus.sonar.plugins</groupId> diff --git a/server/sonar-web/src/main/webapp/META-INF/context.xml b/server/sonar-web/src/main/webapp/META-INF/context.xml deleted file mode 100644 index db181e0854e..00000000000 --- a/server/sonar-web/src/main/webapp/META-INF/context.xml +++ /dev/null @@ -1,4 +0,0 @@ -<Context useHttpOnly="true" reloadable="false" processTlds="false" - useNaming="false" xmlValidation="false" xmlNamespaceAware="false" tldValidation="false" - tldNamespaceAware="false"> -</Context> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/config/logback-access.xml b/server/sonar-web/src/main/webapp/WEB-INF/config/logback-access.xml index e516fcfbbea..504f5fc6797 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/config/logback-access.xml +++ b/server/sonar-web/src/main/webapp/WEB-INF/config/logback-access.xml @@ -14,9 +14,9 @@ <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> - <file>${SONAR_HOME}/logs/access.log</file> + <file>${sonar.path.logs}/access.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> - <param name="FileNamePattern" value="${SONAR_HOME}/logs/access.%i.log"/> + <param name="FileNamePattern" value="${sonar.path.logs}/access.%i.log"/> <param name="MinIndex" value="1"/> <param name="MaxIndex" value="3"/> </rollingPolicy> |