Browse Source

SONAR-6740 refactor configuration of Tomcat contexts

It allows to remove some warnings on static context when
 server fails to start.
tags/5.4-RC1
Simon Brandhof 8 years ago
parent
commit
39cc96f13f

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java View File

@@ -56,7 +56,7 @@ class EmbeddedTomcat {
tomcat.getHost().setDeployOnStartup(true);
new TomcatAccessLog().configure(tomcat, props);
TomcatConnectors.configure(tomcat, props);
webappContext = Webapp.configure(tomcat, props);
webappContext = new TomcatContexts().configure(tomcat, props);
try {
tomcat.start();
new TomcatStartupLogs(props, Loggers.get(getClass())).log(tomcat);

server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java → server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java View File

@@ -19,34 +19,73 @@
*/
package org.sonar.server.app;

import com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletException;
import org.apache.catalina.Context;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.io.FileUtils;
import org.sonar.api.utils.log.Loggers;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;

import static java.lang.String.format;
import static org.apache.commons.lang.StringUtils.isEmpty;

/**
* Configures webapp into Tomcat
* Configures Tomcat contexts:
* <ul>
* <li>/deploy delivers the plugins required by analyzers. It maps directory ${sonar.path.data}/web/deploy.</li>
* <li>/ is the regular webapp</li>
* </ul>
*/
class Webapp {
public class TomcatContexts {

private static final String JRUBY_MAX_RUNTIMES = "jruby.max.runtimes";
private static final String RAILS_ENV = "rails.env";
private static final String ROOT_CONTEXT_PATH = "";
public static final String WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR = "web/deploy";

private final Fs fs;

private Webapp() {
public TomcatContexts() {
this.fs = new Fs();
}

static StandardContext configure(Tomcat tomcat, Props props) {
@VisibleForTesting
TomcatContexts(Fs fs) {
this.fs = fs;
}

public StandardContext configure(Tomcat tomcat, Props props) {
addStaticDir(tomcat, "/deploy", new File(props.nonNullValueAsFile(ProcessProperties.PATH_DATA), WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR));

StandardContext webapp = addContext(tomcat, ROOT_CONTEXT_PATH, webappDir(props));
configureRails(props, webapp);
for (Map.Entry<Object, Object> entry : props.rawProperties().entrySet()) {
String key = entry.getKey().toString();
webapp.addParameter(key, entry.getValue().toString());
}
return webapp;
}

@VisibleForTesting
StandardContext addStaticDir(Tomcat tomcat, String contextPath, File dir) {
try {
// URL /deploy must serve files deployed during startup into DATA_DIR/web/deploy
new WebDeployContext().configureTomcat(tomcat, props);
fs.createOrCleanupDir(dir);
} catch (IOException e) {
throw new IllegalStateException(format("Fail to create or clean-up directory %s", dir.getAbsolutePath()), e);
}

StandardContext context = (StandardContext) tomcat.addWebapp(ROOT_CONTEXT_PATH, webappPath(props));
return addContext(tomcat, contextPath, dir);
}

private StandardContext addContext(Tomcat tomcat, String contextPath, File dir) {
try {
StandardContext context = (StandardContext) tomcat.addWebapp(contextPath, dir.getAbsolutePath());
context.setClearReferencesHttpClientKeepAliveThread(false);
context.setClearReferencesStatic(false);
context.setClearReferencesStopThreads(false);
@@ -61,16 +100,9 @@ class Webapp {
context.setUseNaming(false);
context.setDelegate(true);
context.setJarScanner(new NullJarScanner());
configureRails(props, context);

for (Map.Entry<Object, Object> entry : props.rawProperties().entrySet()) {
String key = entry.getKey().toString();
context.addParameter(key, entry.getValue().toString());
}
return context;

} catch (Exception e) {
throw new IllegalStateException("Fail to configure webapp", e);
} catch (ServletException e) {
throw new IllegalStateException("Fail to configure webapp from " + dir, e);
}
}

@@ -82,19 +114,29 @@ class Webapp {
if (props.valueAsBoolean("sonar.web.dev", false)) {
context.addParameter(RAILS_ENV, "development");
context.addParameter(JRUBY_MAX_RUNTIMES, "3");
Loggers.get(Webapp.class).warn("WEB DEVELOPMENT MODE IS ENABLED - DO NOT USE FOR PRODUCTION USAGE");
Loggers.get(TomcatContexts.class).warn("WEB DEVELOPMENT MODE IS ENABLED - DO NOT USE FOR PRODUCTION USAGE");
} else {
context.addParameter(RAILS_ENV, "production");
context.addParameter(JRUBY_MAX_RUNTIMES, "1");
}
}

static String webappPath(Props props) {
String webDir = props.value("sonar.web.dev.sources");
if (StringUtils.isEmpty(webDir)) {
webDir = new File(props.value(ProcessProperties.PATH_HOME), "web").getAbsolutePath();
static File webappDir(Props props) {
String devDir = props.value("sonar.web.dev.sources");
File dir;
if (isEmpty(devDir)) {
dir = new File(props.value(ProcessProperties.PATH_HOME), "web");
} else {
dir = new File(devDir);
}
Loggers.get(TomcatContexts.class).info("Webapp directory: {}", dir);
return dir;
}

static class Fs {
void createOrCleanupDir(File dir) throws IOException {
FileUtils.forceMkdir(dir);
FileUtils.cleanDirectory(dir);
}
Loggers.get(Webapp.class).info(String.format("Webapp directory: %s", webDir));
return webDir;
}
}

+ 0
- 63
server/sonar-server/src/main/java/org/sonar/server/app/WebDeployContext.java View File

@@ -1,63 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program 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.
*
* This program 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 com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.io.FileUtils;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;

import static java.lang.String.format;

public class WebDeployContext {

public static final String RELATIVE_DIR_IN_DATA = "web/deploy";
private final Fs fs;

public WebDeployContext() {
this(new Fs());
}

@VisibleForTesting
public WebDeployContext(Fs fs) {
this.fs = fs;
}

public void configureTomcat(Tomcat tomcat, Props props) throws ServletException {
File deployDir = new File(props.nonNullValueAsFile(ProcessProperties.PATH_DATA), RELATIVE_DIR_IN_DATA);
try {
fs.createOrCleanupDir(deployDir);
} catch (IOException e) {
throw new IllegalStateException(format("Fail to create or clean-up directory %s", deployDir.getAbsolutePath()), e);
}
tomcat.addWebapp("/deploy", deployDir.getAbsolutePath());
}

static class Fs {
void createOrCleanupDir(File dir) throws IOException {
FileUtils.forceMkdir(dir);
FileUtils.cleanDirectory(dir);
}
}
}

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java View File

@@ -41,7 +41,7 @@ import org.sonar.api.platform.Server;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.process.ProcessProperties;
import org.sonar.server.app.WebDeployContext;
import org.sonar.server.app.TomcatContexts;

import static org.sonar.api.CoreProperties.SERVER_BASE_URL;
import static org.sonar.api.CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE;
@@ -84,7 +84,7 @@ public final class ServerImpl extends Server implements Startable {
throw new IllegalStateException("SonarQube home directory is not valid");
}

deployDir = new File(settings.getString(ProcessProperties.PATH_DATA), WebDeployContext.RELATIVE_DIR_IN_DATA);
deployDir = new File(settings.getString(ProcessProperties.PATH_DATA), TomcatContexts.WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR);

LOG.info("SonarQube {}", Joiner.on(" / ").skipNulls().join("Server", version, implementationBuild));


server/sonar-server/src/test/java/org/sonar/server/app/WebappTest.java → server/sonar-server/src/test/java/org/sonar/server/app/TomcatContextsTest.java View File

@@ -25,56 +25,47 @@ import java.util.Properties;
import org.apache.catalina.Context;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class WebappTest {
public class TomcatContextsTest {

@Rule
public TemporaryFolder temp = new TemporaryFolder();

Props props = new Props(new Properties());
@Rule
public ExpectedException expectedException = ExpectedException.none();

@Before
public void initDataDir() throws Exception {
props.set(ProcessProperties.PATH_DATA, temp.newFolder("data").getAbsolutePath());
}
Tomcat tomcat = mock(Tomcat.class);
Properties props = new Properties();

@Test
public void fail_on_error() throws Exception {
File webDir = temp.newFolder("web");

Tomcat tomcat = mock(Tomcat.class, RETURNS_DEEP_STUBS);
when(tomcat.addContext("", webDir.getAbsolutePath())).thenThrow(new NullPointerException());

try {
Webapp.configure(tomcat, props);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Fail to configure webapp");
}
@Before
public void setUp() throws Exception {
props.setProperty(ProcessProperties.PATH_DATA, temp.newFolder("data").getAbsolutePath());
when(tomcat.addWebapp(anyString(), anyString())).thenReturn(mock(StandardContext.class));
}

@Test
public void configure_context() throws Exception {
props.set("foo", "bar");
public void configure_root_webapp() throws Exception {
props.setProperty("foo", "bar");
StandardContext context = mock(StandardContext.class);
Tomcat tomcat = mock(Tomcat.class);
when(tomcat.addWebapp(anyString(), anyString())).thenReturn(context);

Webapp.configure(tomcat, props);
new TomcatContexts().configure(tomcat, new Props(props));

// configure webapp with properties
verify(context).addParameter("foo", "bar");
@@ -82,23 +73,58 @@ public class WebappTest {

@Test
public void configure_rails_dev_mode() {
props.set("sonar.web.dev", "true");
props.setProperty("sonar.web.dev", "true");
Context context = mock(Context.class);

Webapp.configureRails(props, context);
new TomcatContexts().configureRails(new Props(props), context);

verify(context).addParameter("jruby.max.runtimes", "3");
verify(context).addParameter("rails.env", "development");
}

@Test
public void configure_production_mode() {
props.set("sonar.web.dev", "false");
public void configure_rails_production_mode() {
props.setProperty("sonar.web.dev", "false");
Context context = mock(Context.class);

Webapp.configureRails(props, context);
new TomcatContexts().configureRails(new Props(props), context);

verify(context).addParameter("jruby.max.runtimes", "1");
verify(context).addParameter("rails.env", "production");
}

@Test
public void create_dir_and_configure_static_directory() throws Exception {
File dir = temp.newFolder();
dir.delete();

new TomcatContexts().addStaticDir(tomcat, "/deploy", dir);

assertThat(dir).isDirectory().exists();
verify(tomcat).addWebapp("/deploy", dir.getAbsolutePath());
}

@Test
public void cleanup_static_directory_if_already_exists() throws Exception {
File dir = temp.newFolder();
FileUtils.touch(new File(dir, "foo.txt"));

new TomcatContexts().addStaticDir(tomcat, "/deploy", dir);

assertThat(dir).isDirectory().exists();
assertThat(dir.listFiles()).isEmpty();
}

@Test
public void fail_if_static_directory_can_not_be_initialized() throws Exception {
File dir = temp.newFolder();
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Fail to create or clean-up directory " + dir.getAbsolutePath());

TomcatContexts.Fs fs = mock(TomcatContexts.Fs.class);
doThrow(new IOException()).when(fs).createOrCleanupDir(any(File.class));

new TomcatContexts(fs).addStaticDir(tomcat, "/deploy", dir);

}
}

+ 0
- 87
server/sonar-server/src/test/java/org/sonar/server/app/WebDeployContextTest.java View File

@@ -1,87 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program 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.
*
* This program 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 java.io.File;
import java.io.IOException;
import java.util.Properties;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

public class WebDeployContextTest {

@Rule
public TemporaryFolder temp = new TemporaryFolder();

@Rule
public ExpectedException expectedException = ExpectedException.none();

Tomcat tomcat = mock(Tomcat.class);
Properties props = new Properties();

@Test
public void create_dir_and_configure_tomcat_context() throws Exception {
File dataDir = temp.newFolder();
props.setProperty(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath());
new WebDeployContext().configureTomcat(tomcat, new Props(props));

File deployDir = new File(dataDir, "web/deploy");
assertThat(deployDir).isDirectory().exists();
verify(tomcat).addWebapp("/deploy", deployDir.getAbsolutePath());
}

@Test
public void cleanup_directory_if_already_exists() throws Exception {
File dataDir = temp.newFolder();
File deployDir = new File(dataDir, "web/deploy");
FileUtils.touch(new File(deployDir, "foo.txt"));
props.setProperty(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath());
new WebDeployContext().configureTomcat(tomcat, new Props(props));

assertThat(deployDir).isDirectory().exists();
assertThat(deployDir.listFiles()).isEmpty();
}

@Test
public void fail_if_directory_can_not_be_initialized() throws Exception {
File dataDir = temp.newFolder();
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Fail to create or clean-up directory " + dataDir.getAbsolutePath());

props.setProperty(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath());
WebDeployContext.Fs fs = mock(WebDeployContext.Fs.class);
doThrow(new IOException()).when(fs).createOrCleanupDir(any(File.class));

new WebDeployContext(fs).configureTomcat(tomcat, new Props(props));

}
}

Loading…
Cancel
Save