aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2013-10-14 01:00:12 +0200
committerSimon Brandhof <simon.brandhof@gmail.com>2013-10-14 01:00:12 +0200
commit275da1c743917341256322a09a3172a2ccf04ef3 (patch)
tree42f62b18140966f7da89847f6033ce8691700285
parent5b4b4172c45aa794d7b40c8e5b9fde6d3a001492 (diff)
downloadsonarqube-275da1c743917341256322a09a3172a2ccf04ef3.tar.gz
sonarqube-275da1c743917341256322a09a3172a2ccf04ef3.zip
SONAR-4741 HTTPS mode
-rw-r--r--sonar-application/pom.xml6
-rw-r--r--sonar-application/src/main/assembly/conf/sonar.properties92
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Connectors.java105
-rw-r--r--sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java10
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Logging.java22
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Props.java7
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Webapp.java10
-rw-r--r--sonar-application/src/test/java/org/sonar/application/ConnectorsTest.java141
-rw-r--r--sonar-application/src/test/java/org/sonar/application/LoggingTest.java17
-rw-r--r--sonar-application/src/test/java/org/sonar/application/PropsTest.java13
-rw-r--r--sonar-application/src/test/java/org/sonar/application/WebappTest.java22
11 files changed, 357 insertions, 88 deletions
diff --git a/sonar-application/pom.xml b/sonar-application/pom.xml
index a55c24d7c99..5a63c20575e 100644
--- a/sonar-application/pom.xml
+++ b/sonar-application/pom.xml
@@ -223,6 +223,12 @@
<artifactId>http-request</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
<build>
diff --git a/sonar-application/src/main/assembly/conf/sonar.properties b/sonar-application/src/main/assembly/conf/sonar.properties
index 3b4fc79cd0e..1ef1d031316 100644
--- a/sonar-application/src/main/assembly/conf/sonar.properties
+++ b/sonar-application/src/main/assembly/conf/sonar.properties
@@ -11,34 +11,6 @@
#--------------------------------------------------------------------------------------------------
-# WEB SERVER
-
-# Binding address
-#sonar.web.host=0.0.0.0
-
-# TCP port for incoming HTTP connections
-#sonar.web.port=9000
-
-# Web context must start with slash (/)
-#sonar.web.context=/
-
-# The maximum number of connections that the server will accept and process at any given time.
-# When this number has been reached, the server will not accept any more connections until
-# the number of connections falls below this value. The operating system may still accept connections
-# based on the sonar.web.connections.acceptCount property. The default value is 50.
-#sonar.web.connections.maxThreads=50
-
-# The minimum number of threads always kept running. If not specified, the default of 5 is used.
-#sonar.web.connections.minThreads=5
-
-# The maximum queue length for incoming connection requests when all possible request processing
-# threads are in use. Any requests received when the queue is full will be refused.
-# The default value is 25.
-#sonar.web.connections.acceptCount=25
-
-
-
-#--------------------------------------------------------------------------------------------------
# DATABASE
#
# IMPORTANT: the embedded H2 database is used by default. It is recommended for tests only.
@@ -112,6 +84,70 @@ sonar.jdbc.timeBetweenEvictionRunsMillis=30000
#--------------------------------------------------------------------------------------------------
+# WEB SERVER
+
+# Binding address
+#sonar.web.host=0.0.0.0
+
+# Web context. When set, it must start with forward slash (for example /sonarqube).
+# The default value is root context (empty value).
+#sonar.web.context=
+
+# TCP port for incoming HTTP connections
+#sonar.web.http.enable=true
+#sonar.web.port=9000
+
+# TCP port for incoming HTTPS connections. Disabled by default.
+#sonar.web.https.enable=false
+#sonar.web.https.port=9443
+
+# HTTPS - the alias used to for the server certificate in the keystore.
+# If not specified the first key read in the keystore is used.
+#sonar.web.https.keyAlias=
+
+# HTTPS - the password used to access the server certificate from the
+# specified keystore file. The default value is "changeit".
+#sonar.web.https.keyPass=changeit
+
+# HTTPS - the pathname of the keystore file where is stored the server certificate.
+# By default, the pathname is the file ".keystore" in the user home.
+# If keystoreType doesn't need a file use empty value.
+#sonar.web.https.keystoreFile=
+
+# HTTPS - the password used to access the specified keystore file. The default
+# value is the value of sonar.web.https.keyPass.
+#sonar.web.https.keystorePass=
+
+# HTTPS - the type of keystore file to be used for the server certificate.
+# The default value is JKS (Java KeyStore).
+#sonar.web.https.keystoreType=JKS
+
+# HTTPS - the name of the keystore provider to be used for the server certificate.
+# If not specified, the list of registered providers is traversed in preference order
+# and the first provider that supports the keystore type is used (see sonar.web.https.keystoreType).
+#sonar.web.https.keystoreProvider=
+
+# The maximum number of connections that the server will accept and process at any given time.
+# When this number has been reached, the server will not accept any more connections until
+# the number of connections falls below this value. The operating system may still accept connections
+# based on the sonar.web.connections.acceptCount property. The default value is 50 for each
+# enabled connector.
+#sonar.web.http.maxThreads=50
+#sonar.web.https.maxThreads=50
+
+# The minimum number of threads always kept running. If not specified, the default of 5 is used.
+#sonar.web.http.minThreads=5
+#sonar.web.https.minThreads=5
+
+# The maximum queue length for incoming connection requests when all possible request processing
+# threads are in use. Any requests received when the queue is full will be refused.
+# The default value is 25 for each enabled connector.
+#sonar.web.http.acceptCount=25
+#sonar.web.https.acceptCount=25
+
+
+
+#--------------------------------------------------------------------------------------------------
# UPDATE CENTER
# The Update Center requires an internet connection to request http://update.sonarsource.org
diff --git a/sonar-application/src/main/java/org/sonar/application/Connectors.java b/sonar-application/src/main/java/org/sonar/application/Connectors.java
index 9dac5526d6e..77609d1820d 100644
--- a/sonar-application/src/main/java/org/sonar/application/Connectors.java
+++ b/sonar-application/src/main/java/org/sonar/application/Connectors.java
@@ -21,42 +21,99 @@ package org.sonar.application;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
+import org.slf4j.LoggerFactory;
-class Connectors {
+import javax.annotation.Nullable;
- static final String PROPERTY_SHUTDOWN_TOKEN = "sonar.web.shutdown.token";
- static final String PROPERTY_SHUTDOWN_PORT = "sonar.web.shutdown.port";
- static final String PROPERTY_MIN_THREADS = "sonar.web.connections.minThreads";
- static final String PROPERTY_MAX_THREADS = "sonar.web.connections.maxThreads";
- static final String PROPERTY_ACCEPT_COUNT = "sonar.web.connections.acceptCount";
+class Connectors {
static void configure(Tomcat tomcat, Props props) {
tomcat.getServer().setAddress(props.of("sonar.web.host", "0.0.0.0"));
configureShutdown(tomcat, props);
+ configureConnectors(tomcat, props);
+ }
- Connector connector = new Connector("HTTP/1.1");
- connector.setPort(props.intOf("sonar.web.port", 9000));
- connector.setURIEncoding("UTF-8");
- configurePool(props, connector);
- configureCompression(connector);
- tomcat.setConnector(connector);
- tomcat.getService().addConnector(connector);
+ private static void configureConnectors(Tomcat tomcat, Props props) {
+ Connector http = newHttpConnector(props);
+ Connector https = newHttpsConnector(props);
+
+ if (http == null) {
+ if (https == null) {
+ throw new IllegalStateException("Both HTTP and HTTPS connectors are disabled");
+ }
+ // Enable only HTTPS
+ tomcat.setConnector(https);
+ tomcat.getService().addConnector(https);
+ } else {
+ // Both HTTP and HTTPS are enabled
+ tomcat.setConnector(http);
+ tomcat.getService().addConnector(http);
+ if (https != null) {
+ tomcat.getService().addConnector(https);
+ }
+ }
}
private static void configureShutdown(Tomcat tomcat, Props props) {
- String shutdownToken = props.of(PROPERTY_SHUTDOWN_TOKEN);
- Integer shutdownPort = props.intOf(PROPERTY_SHUTDOWN_PORT);
+ String shutdownToken = props.of("sonar.web.shutdown.token");
+ Integer shutdownPort = props.intOf("sonar.web.shutdown.port");
if (shutdownToken != null && !"".equals(shutdownToken) && shutdownPort != null) {
tomcat.getServer().setPort(shutdownPort);
tomcat.getServer().setShutdown(shutdownToken);
+ info("Shutdown command is enabled on port " + shutdownPort);
+ }
+ }
+
+ @Nullable
+ private static Connector newHttpConnector(Props props) {
+ Connector connector = null;
+ if (props.booleanOf("sonar.web.http.enable", true)) {
+ connector = newConnector(props, "http");
+ // Not named "sonar.web.http.port" to keep backward-compatibility
+ int port = props.intOf("sonar.web.port", 9000);
+ connector.setPort(port);
+ info("HTTP connector is enabled on port " + port);
+ }
+ return connector;
+ }
+
+ @Nullable
+ private static Connector newHttpsConnector(Props props) {
+ Connector connector = null;
+ if (props.booleanOf("sonar.web.https.enable")) {
+ connector = newConnector(props, "https");
+ int port = props.intOf("sonar.web.https.port", 9443);
+ 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, "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 scheme) {
+ Connector connector = new Connector("HTTP/1.1");
+ connector.setURIEncoding("UTF-8");
+ configurePool(props, connector, scheme);
+ configureCompression(connector);
+ return connector;
}
- private static void configurePool(Props props, Connector connector) {
+ private static void configurePool(Props props, Connector connector, String scheme) {
connector.setProperty("acceptorThreadCount", String.valueOf(2));
- connector.setProperty("minSpareThreads", String.valueOf(props.intOf(PROPERTY_MIN_THREADS, 5)));
- connector.setProperty("maxThreads", String.valueOf(props.intOf(PROPERTY_MAX_THREADS, 50)));
- connector.setProperty("acceptCount", String.valueOf(props.intOf(PROPERTY_ACCEPT_COUNT, 25)));
+ 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) {
@@ -64,4 +121,14 @@ class Connectors {
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/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java b/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java
index 4838e3d2202..b92783982d3 100644
--- a/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java
+++ b/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java
@@ -19,7 +19,10 @@
*/
package org.sonar.application;
+import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.io.FileUtils;
@@ -67,7 +70,6 @@ class EmbeddedTomcat {
Logging.configure(tomcat, env);
Connectors.configure(tomcat, props);
Webapp.configure(tomcat, env, props);
-
tomcat.start();
addShutdownHook();
tomcat.getServer().await();
@@ -117,6 +119,10 @@ class EmbeddedTomcat {
}
int port() {
- return tomcat.getService().findConnectors()[0].getLocalPort();
+ Connector[] connectors = tomcat.getService().findConnectors();
+ if (connectors.length > 0) {
+ return connectors[0].getLocalPort();
+ }
+ return -1;
}
}
diff --git a/sonar-application/src/main/java/org/sonar/application/Logging.java b/sonar-application/src/main/java/org/sonar/application/Logging.java
index 29d8d097698..ae5a5a4d6b3 100644
--- a/sonar-application/src/main/java/org/sonar/application/Logging.java
+++ b/sonar-application/src/main/java/org/sonar/application/Logging.java
@@ -20,7 +20,11 @@
package org.sonar.application;
import ch.qos.logback.access.tomcat.LogbackValve;
+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 java.io.File;
@@ -38,6 +42,8 @@ class Logging {
static void configure(Tomcat tomcat, Env env) {
tomcat.setSilent(false);
+ tomcat.getService().addLifecycleListener(new StartupLogger(LoggerFactory.getLogger(Logging.class)));
+
LogbackValve valve = new LogbackValve();
valve.setQuiet(true);
File confFile = env.file(CONF_PATH);
@@ -47,4 +53,20 @@ class Logging {
valve.setFilename(confFile.getAbsolutePath());
tomcat.getHost().getPipeline().addValve(valve);
}
+
+ static class StartupLogger implements LifecycleListener {
+ private Logger logger;
+
+ StartupLogger(Logger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ public void lifecycleEvent(LifecycleEvent event) {
+ if ("after_start".equals(event.getType())) {
+ logger.info("Web server is up");
+ }
+ }
+ }
+
}
diff --git a/sonar-application/src/main/java/org/sonar/application/Props.java b/sonar-application/src/main/java/org/sonar/application/Props.java
index f720553c18a..4e0bb6c3d20 100644
--- a/sonar-application/src/main/java/org/sonar/application/Props.java
+++ b/sonar-application/src/main/java/org/sonar/application/Props.java
@@ -29,7 +29,7 @@ import java.io.IOException;
import java.util.Properties;
/**
- * TODO support env substitution
+ * TODO support env substitution and encryption
*/
class Props {
private final Properties props;
@@ -52,6 +52,11 @@ class Props {
return s != null && Boolean.parseBoolean(s);
}
+ boolean booleanOf(String key, boolean defaultValue) {
+ String s = of(key);
+ return s != null ? Boolean.parseBoolean(s) : defaultValue;
+ }
+
Integer intOf(String key) {
String s = of(key);
if (s != null && !"".equals(s)) {
diff --git a/sonar-application/src/main/java/org/sonar/application/Webapp.java b/sonar-application/src/main/java/org/sonar/application/Webapp.java
index aa85ea7d1b5..89f84c3603a 100644
--- a/sonar-application/src/main/java/org/sonar/application/Webapp.java
+++ b/sonar-application/src/main/java/org/sonar/application/Webapp.java
@@ -28,7 +28,7 @@ class Webapp {
private static final String RAILS_ENV = "rails.env";
static void configure(Tomcat tomcat, Env env, Props props) {
- String ctx = props.of("sonar.web.context", "/");
+ String ctx = getContext(props);
try {
Context context = tomcat.addWebapp(ctx, env.file("web").getAbsolutePath());
context.setConfigFile(env.file("web/META-INF/context.xml").toURI().toURL());
@@ -40,6 +40,14 @@ class Webapp {
}
}
+ static String getContext(Props props) {
+ String context = props.of("sonar.web.context", "");
+ if (!"".equals(context) && !context.startsWith("/")) {
+ throw new IllegalStateException("Value of sonar.web.context must start with a forward slash: " + context);
+ }
+ return context;
+ }
+
static void configureRailsMode(Props props, Context context) {
if (props.booleanOf("sonar.web.dev")) {
context.addParameter(RAILS_ENV, "development");
diff --git a/sonar-application/src/test/java/org/sonar/application/ConnectorsTest.java b/sonar-application/src/test/java/org/sonar/application/ConnectorsTest.java
index b40b79b32f0..c1594b2c46a 100644
--- a/sonar-application/src/test/java/org/sonar/application/ConnectorsTest.java
+++ b/sonar-application/src/test/java/org/sonar/application/ConnectorsTest.java
@@ -19,98 +19,167 @@
*/
package org.sonar.application;
+import com.google.common.collect.ImmutableMap;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
+import java.util.Map;
import java.util.Properties;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.*;
public class ConnectorsTest {
+
+ Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS);
+
+ //---- connectors
+
@Test
- public void enable_shutdown_port() throws Exception {
+ public void configure_thread_pool() throws Exception {
Properties p = new Properties();
- p.setProperty(Connectors.PROPERTY_SHUTDOWN_PORT, "9010");
- p.setProperty(Connectors.PROPERTY_SHUTDOWN_TOKEN, "SHUTDOWN");
+ p.setProperty("sonar.web.http.minThreads", "2");
+ p.setProperty("sonar.web.http.maxThreads", "30");
+ p.setProperty("sonar.web.http.acceptCount", "20");
Props props = new Props(p);
- Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS);
Connectors.configure(tomcat, props);
- verify(tomcat.getServer()).setPort(9010);
- verify(tomcat.getServer()).setShutdown("SHUTDOWN");
+ verify(tomcat).setConnector(argThat(new PropertiesMatcher(
+ ImmutableMap.<String, Object>of("minSpareThreads", 2, "maxThreads", 30, "acceptCount", 20)
+ )));
}
@Test
- public void disable_shutdown_port_by_default() throws Exception {
+ public void configure_default_thread_pool() throws Exception {
Props props = new Props(new Properties());
- Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS);
Connectors.configure(tomcat, props);
- verify(tomcat.getServer(), never()).setPort(anyInt());
- verify(tomcat.getServer(), never()).setShutdown(anyString());
+ verify(tomcat).setConnector(argThat(new PropertiesMatcher(
+ ImmutableMap.<String, Object>of("minSpareThreads", 5, "maxThreads", 50, "acceptCount", 25)
+ )));
}
@Test
- public void disable_shutdown_port_if_missing_token() throws Exception {
+ public void fail_if_both_http_and_https_are_disabled() {
Properties p = new Properties();
- // only the port, but not the token
- p.setProperty(Connectors.PROPERTY_SHUTDOWN_PORT, "9010");
+ p.setProperty("sonar.web.http.enable", "false");
+ p.setProperty("sonar.web.https.enable", "false");
Props props = new Props(p);
- Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS);
- Connectors.configure(tomcat, props);
-
- verify(tomcat.getServer(), never()).setPort(anyInt());
- verify(tomcat.getServer(), never()).setShutdown(anyString());
+ try {
+ Connectors.configure(tomcat, props);
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e.getMessage()).isEqualTo("Both HTTP and HTTPS connectors are disabled");
+ }
}
@Test
- public void configure_thread_pool() throws Exception {
+ public void only_https_is_enabled() {
Properties p = new Properties();
- p.setProperty(Connectors.PROPERTY_MIN_THREADS, "2");
- p.setProperty(Connectors.PROPERTY_MAX_THREADS, "30");
- p.setProperty(Connectors.PROPERTY_ACCEPT_COUNT, "20");
+ p.setProperty("sonar.web.http.enable", "false");
+ p.setProperty("sonar.web.https.enable", "true");
Props props = new Props(p);
- Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS);
Connectors.configure(tomcat, props);
verify(tomcat).setConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
public boolean matches(Object o) {
Connector c = (Connector)o;
- return (Integer)c.getProperty("minSpareThreads") == 2 &&
- (Integer) c.getProperty("maxThreads") == 30 &&
- (Integer) c.getProperty("acceptCount") == 20;
+ return c.getScheme().equals("https") && c.getPort()==9443;
}
}));
}
@Test
- public void configure_default_thread_pool() throws Exception {
- Props props = new Props(new Properties());
+ public void both_http_and_https_are_enabled() {
+ Properties p = new Properties();
+ p.setProperty("sonar.web.http.enable", "true");
+ p.setProperty("sonar.web.https.enable", "true");
+ Props props = new Props(p);
- Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS);
Connectors.configure(tomcat, props);
- verify(tomcat).setConnector(argThat(new ArgumentMatcher<Connector>() {
+ verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
public boolean matches(Object o) {
Connector c = (Connector)o;
- return (Integer)c.getProperty("minSpareThreads") == 5 &&
- (Integer) c.getProperty("maxThreads") == 50 &&
- (Integer) c.getProperty("acceptCount") == 25;
+ return c.getScheme().equals("http") && c.getPort()==9000;
}
}));
+ verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() {
+ @Override
+ public boolean matches(Object o) {
+ Connector c = (Connector)o;
+ return c.getScheme().equals("https") && c.getPort()==9443;
+ }
+ }));
+ }
+
+
+ //---- shutdown port
+
+ @Test
+ public void enable_shutdown_port() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("sonar.web.shutdown.port", "9010");
+ p.setProperty("sonar.web.shutdown.token", "SHUTDOWN");
+ Props props = new Props(p);
+
+ Connectors.configure(tomcat, props);
+
+ verify(tomcat.getServer()).setPort(9010);
+ verify(tomcat.getServer()).setShutdown("SHUTDOWN");
+ }
+
+ @Test
+ public void disable_shutdown_port_by_default() throws Exception {
+ Props props = new Props(new Properties());
+
+ Connectors.configure(tomcat, props);
+
+ verify(tomcat.getServer(), never()).setPort(anyInt());
+ verify(tomcat.getServer(), never()).setShutdown(anyString());
+ }
+
+ @Test
+ public void disable_shutdown_port_if_missing_token() throws Exception {
+ Properties p = new Properties();
+ // only the port, but not the token
+ p.setProperty("sonar.web.shutdown.port", "9010");
+ Props props = new Props(p);
+
+ Connectors.configure(tomcat, props);
+
+ verify(tomcat.getServer(), never()).setPort(anyInt());
+ verify(tomcat.getServer(), never()).setShutdown(anyString());
+ }
+
+ private static class PropertiesMatcher extends ArgumentMatcher<Connector> {
+ private final Map<String, Object> expected;
+
+ PropertiesMatcher(Map<String, Object> expected) {
+ this.expected = expected;
+ }
+
+ public boolean matches(Object o) {
+ Connector c = (Connector) o;
+ for (Map.Entry<String, Object> entry : expected.entrySet()) {
+ if (!entry.getValue().equals(c.getProperty(entry.getKey()))) {
+ return false;
+ }
+ }
+ return true;
+ }
}
}
diff --git a/sonar-application/src/test/java/org/sonar/application/LoggingTest.java b/sonar-application/src/test/java/org/sonar/application/LoggingTest.java
index ca5f6ef3c51..39808394b88 100644
--- a/sonar-application/src/test/java/org/sonar/application/LoggingTest.java
+++ b/sonar-application/src/test/java/org/sonar/application/LoggingTest.java
@@ -20,11 +20,14 @@
package org.sonar.application;
import ch.qos.logback.access.tomcat.LogbackValve;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.Valve;
import org.apache.catalina.startup.Tomcat;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
+import org.slf4j.Logger;
import java.io.File;
@@ -65,4 +68,18 @@ public class LoggingTest {
assertThat(e).hasMessage("File is missing: " + confFile.getAbsolutePath());
}
}
+
+ @Test
+ public void log_when_started() {
+ Logger logger = mock(Logger.class);
+ Logging.StartupLogger listener = new Logging.StartupLogger(logger);
+
+ LifecycleEvent event = new LifecycleEvent(mock(Lifecycle.class), "before_init", null);
+ listener.lifecycleEvent(event);
+ verifyZeroInteractions(logger);
+
+ event = new LifecycleEvent(mock(Lifecycle.class), "after_start", null);
+ listener.lifecycleEvent(event);
+ verify(logger).info("Web server is up");
+ }
}
diff --git a/sonar-application/src/test/java/org/sonar/application/PropsTest.java b/sonar-application/src/test/java/org/sonar/application/PropsTest.java
index f6c1f2ece01..20ca51e5161 100644
--- a/sonar-application/src/test/java/org/sonar/application/PropsTest.java
+++ b/sonar-application/src/test/java/org/sonar/application/PropsTest.java
@@ -85,6 +85,19 @@ public class PropsTest {
}
@Test
+ public void booleanOf_default_value() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("foo", "true");
+ p.setProperty("bar", "false");
+ Props props = new Props(p);
+
+ assertThat(props.booleanOf("unset", false)).isFalse();
+ assertThat(props.booleanOf("unset", true)).isTrue();
+ assertThat(props.booleanOf("foo", false)).isTrue();
+ assertThat(props.booleanOf("bar", true)).isFalse();
+ }
+
+ @Test
public void load_file_and_system_properties() throws Exception {
Env env = mock(Env.class);
File propsFile = new File(getClass().getResource("/org/sonar/application/PropsTest/sonar.properties").toURI());
diff --git a/sonar-application/src/test/java/org/sonar/application/WebappTest.java b/sonar-application/src/test/java/org/sonar/application/WebappTest.java
index 0e0dd904a1c..aa1ba82eee8 100644
--- a/sonar-application/src/test/java/org/sonar/application/WebappTest.java
+++ b/sonar-application/src/test/java/org/sonar/application/WebappTest.java
@@ -26,6 +26,7 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
+import java.util.Properties;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
@@ -46,7 +47,7 @@ public class WebappTest {
when(tomcat.addContext("", webDir.getAbsolutePath())).thenThrow(new NullPointerException());
try {
- Webapp.configure(tomcat, env, mock(Props.class));
+ Webapp.configure(tomcat, env, new Props(new Properties()));
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Fail to configure webapp");
@@ -76,4 +77,23 @@ public class WebappTest {
verify(context).addParameter("jruby.max.runtimes", "1");
verify(context).addParameter("rails.env", "production");
}
+
+ @Test
+ public void context_must_start_with_slash() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("sonar.web.context", "foo");
+
+ try {
+ Webapp.getContext(new Props(p));
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e.getMessage()).isEqualTo("Value of sonar.web.context must start with a forward slash: foo");
+ }
+ }
+
+ @Test
+ public void default_context_is_root() throws Exception {
+ String context = Webapp.getContext(new Props(new Properties()));
+ assertThat(context).isEqualTo("");
+ }
}