diff options
author | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2016-01-13 11:24:01 +0100 |
---|---|---|
committer | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2016-01-18 11:16:44 +0100 |
commit | e7c4a306ac47843029d183d8e3f5ca7730184cf2 (patch) | |
tree | d0ad97d750a92e90c60c280a856f6a2b75a1b6b5 /sonar-application | |
parent | 5e12a8cf6011e4f95405a1f8abeee38c92245141 (diff) | |
download | sonarqube-e7c4a306ac47843029d183d8e3f5ca7730184cf2.tar.gz sonarqube-e7c4a306ac47843029d183d8e3f5ca7730184cf2.zip |
SONAR-7168 fileSystem must be reset during a restart
deletion of temp directory is required for uninstalling plugins but also to cleanly start web server (eg. deleting unsuccessfully processed analysis reports)
Diffstat (limited to 'sonar-application')
5 files changed, 341 insertions, 107 deletions
diff --git a/sonar-application/src/main/java/org/sonar/application/App.java b/sonar-application/src/main/java/org/sonar/application/App.java index e09899b1fa4..ca2f98d62b8 100644 --- a/sonar-application/src/main/java/org/sonar/application/App.java +++ b/sonar-application/src/main/java/org/sonar/application/App.java @@ -39,8 +39,8 @@ public class App implements Stoppable { private final Monitor monitor; - public App(File tempDir) { - this(Monitor.create(tempDir)); + public App(AppFileSystem appFileSystem, boolean watchForHardStop) { + this(Monitor.create(appFileSystem, watchForHardStop)); } App(Monitor monitor) { @@ -48,14 +48,11 @@ public class App implements Stoppable { } public void start(Props props) { - if (props.valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND, false)) { - monitor.watchForHardStop(); - } monitor.start(createCommands(props)); monitor.awaitTermination(); } - private List<JavaCommand> createCommands(Props props) { + private static List<JavaCommand> createCommands(Props props) { List<JavaCommand> commands = new ArrayList<>(); File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME); JavaCommand elasticsearch = new JavaCommand("search"); @@ -102,11 +99,14 @@ public class App implements Stoppable { CommandLineParser cli = new CommandLineParser(); Properties rawProperties = cli.parseArguments(args); Props props = new PropsBuilder(rawProperties, new JdbcSettings()).build(); + AppFileSystem appFileSystem = new AppFileSystem(props); + appFileSystem.verifyProps(); AppLogging logging = new AppLogging(); logging.configure(props); - File tempDir = props.nonNullValueAsFile(ProcessProperties.PATH_TEMP); - App app = new App(tempDir); + // used by orchestrator + boolean watchForHardStop = props.valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND, false); + App app = new App(appFileSystem, watchForHardStop); app.start(props); } diff --git a/sonar-application/src/main/java/org/sonar/application/AppFileSystem.java b/sonar-application/src/main/java/org/sonar/application/AppFileSystem.java new file mode 100644 index 00000000000..32f563737b2 --- /dev/null +++ b/sonar-application/src/main/java/org/sonar/application/AppFileSystem.java @@ -0,0 +1,120 @@ +/* + * 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.application; + +import java.io.File; +import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.process.Props; +import org.sonar.process.monitor.FileSystem; + +import static org.apache.commons.io.FileUtils.deleteQuietly; +import static org.apache.commons.io.FileUtils.forceMkdir; +import static org.sonar.process.ProcessProperties.PATH_DATA; +import static org.sonar.process.ProcessProperties.PATH_HOME; +import static org.sonar.process.ProcessProperties.PATH_LOGS; +import static org.sonar.process.ProcessProperties.PATH_TEMP; +import static org.sonar.process.ProcessProperties.PATH_WEB; + +public class AppFileSystem implements FileSystem { + private static final Logger LOG = LoggerFactory.getLogger(AppFileSystem.class); + + private static final String DEFAULT_DATA_DIRECTORY_NAME = "data"; + private static final String DEFAULT_WEB_DIRECTORY_NAME = "web"; + private static final String DEFAULT_LOGS_DIRECTORY_NAME = "logs"; + private static final String DEFAULT_TEMP_DIRECTORY_NAME = "temp"; + + private final Props props; + private final File homeDir; + private boolean initialized = false; + + public AppFileSystem(Props props) { + this.props = props; + this.homeDir = props.nonNullValueAsFile(PATH_HOME); + } + + public void verifyProps() { + ensurePropertyIsAbsolutePath(props, PATH_DATA, DEFAULT_DATA_DIRECTORY_NAME); + ensurePropertyIsAbsolutePath(props, PATH_WEB, DEFAULT_WEB_DIRECTORY_NAME); + ensurePropertyIsAbsolutePath(props, PATH_LOGS, DEFAULT_LOGS_DIRECTORY_NAME); + ensurePropertyIsAbsolutePath(props, PATH_TEMP, DEFAULT_TEMP_DIRECTORY_NAME); + this.initialized = true; + } + + /** + * Must be called after {@link #verifyProps()} + */ + @Override + public void reset() throws IOException { + if (!initialized) { + throw new IllegalStateException("method verifyProps must be called first"); + } + ensureDirectoryExists(props, PATH_DATA); + ensureDirectoryExists(props, PATH_WEB); + ensureDirectoryExists(props, PATH_LOGS); + createOrCleanDirectory(props, PATH_TEMP); + } + + @Override + public File getTempDir() { + return props.nonNullValueAsFile(PATH_TEMP); + } + + private File ensurePropertyIsAbsolutePath(Props props, String propKey, String defaultRelativePath) { + String path = props.value(propKey, defaultRelativePath); + File d = new File(path); + if (!d.isAbsolute()) { + d = new File(homeDir, path); + LOG.trace("Overriding property {} from relative path '{}' to absolute path '{}'", path, d.getAbsolutePath()); + props.set(propKey, d.getAbsolutePath()); + } + return d; + } + + private static boolean ensureDirectoryExists(Props props, String propKey) throws IOException { + File dir = props.nonNullValueAsFile(propKey); + if (dir.exists()) { + ensureIsNotAFile(propKey, dir); + return false; + } else { + LOG.trace("forceMkdir {}", dir.getAbsolutePath()); + forceMkdir(dir); + ensureIsNotAFile(propKey, dir); + return true; + } + } + + private static void ensureIsNotAFile(String propKey, File dir) { + if (!dir.isDirectory()) { + throw new IllegalStateException(String.format("Property '%s' is not valid, not a directory: %s", + propKey, dir.getAbsolutePath())); + } + } + + private static void createOrCleanDirectory(Props props, String propKey) throws IOException { + File dir = props.nonNullValueAsFile(propKey); + LOG.info("Deleting and/or creating temp directory {}", dir.getAbsolutePath()); + if (!ensureDirectoryExists(props, propKey)) { + deleteQuietly(dir); + forceMkdir(dir); + } + } +} diff --git a/sonar-application/src/main/java/org/sonar/application/PropsBuilder.java b/sonar-application/src/main/java/org/sonar/application/PropsBuilder.java index d915e1609ad..1e2d25778e2 100644 --- a/sonar-application/src/main/java/org/sonar/application/PropsBuilder.java +++ b/sonar-application/src/main/java/org/sonar/application/PropsBuilder.java @@ -19,12 +19,6 @@ */ package org.sonar.application; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.sonar.process.ConfigurationUtils; -import org.sonar.process.ProcessProperties; -import org.sonar.process.Props; - import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -33,6 +27,10 @@ import java.io.Reader; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.Properties; +import org.apache.commons.io.IOUtils; +import org.sonar.process.ConfigurationUtils; +import org.sonar.process.ProcessProperties; +import org.sonar.process.Props; class PropsBuilder { @@ -51,8 +49,7 @@ class PropsBuilder { } /** - * Load optional conf/sonar.properties, interpolates environment variables and - * initializes file system + * Load optional conf/sonar.properties, interpolates environment variables */ Props build() throws IOException { Properties p = loadPropertiesFile(homeDir); @@ -66,12 +63,6 @@ class PropsBuilder { Props props = new Props(p); ProcessProperties.completeDefaults(props); - // init file system - initExistingDir(props, ProcessProperties.PATH_DATA, "data"); - initExistingDir(props, ProcessProperties.PATH_WEB, "web"); - initExistingDir(props, ProcessProperties.PATH_LOGS, "logs"); - initTempDir(props); - // check JDBC properties and set path to driver jdbcSettings.checkAndComplete(homeDir, props); @@ -96,31 +87,4 @@ class PropsBuilder { } return p; } - - private void initTempDir(Props props) throws IOException { - File dir = configureDir(props, ProcessProperties.PATH_TEMP, "temp"); - FileUtils.deleteQuietly(dir); - FileUtils.forceMkdir(dir); - } - - private void initExistingDir(Props props, String propKey, String defaultRelativePath) throws IOException { - File dir = configureDir(props, propKey, defaultRelativePath); - if (!dir.exists()) { - FileUtils.forceMkdir(dir); - } - if (!dir.isDirectory()) { - throw new IllegalStateException(String.format("Property '%s' is not valid, not a directory: %s", - propKey, dir.getAbsolutePath())); - } - } - - private File configureDir(Props props, String propKey, String defaultRelativePath) { - String path = props.value(propKey, defaultRelativePath); - File d = new File(path); - if (!d.isAbsolute()) { - d = new File(homeDir, path); - } - props.set(propKey, d.getAbsolutePath()); - return d; - } } diff --git a/sonar-application/src/test/java/org/sonar/application/AppFileSystemTest.java b/sonar-application/src/test/java/org/sonar/application/AppFileSystemTest.java new file mode 100644 index 00000000000..baac93fa203 --- /dev/null +++ b/sonar-application/src/test/java/org/sonar/application/AppFileSystemTest.java @@ -0,0 +1,202 @@ +/* + * 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.application; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; +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.Props; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AppFileSystemTest { + + private static final String PROPERTY_SONAR_PATH_WEB = "sonar.path.web"; + private static final String PROPERTY_SONAR_PATH_DATA = "sonar.path.data"; + private static final String PROPERTY_SONAR_PATH_LOGS = "sonar.path.logs"; + private static final String PROPERTY_SONAR_PATH_TEMP = "sonar.path.temp"; + private static final String NON_DEFAULT_DATA_DIR_NAME = "toto"; + private static final String NON_DEFAULT_WEB_DIR_NAME = "tutu"; + private static final String NON_DEFAULT_LOGS_DIR_NAME = "titi"; + private static final String NON_DEFAULT_TEMP_DIR_NAME = "tatta"; + private static final String DEFAULT_DATA_DIR_NAME = "data"; + private static final String DEFAULT_WEB_DIR_NAME = "web"; + private static final String DEFAULT_LOGS_DIR_NAME = "logs"; + private static final String DEFAULT_TEMP_DIR_NAME = "temp"; + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private File homeDir; + private Properties properties; + + @Before + public void before() throws IOException { + homeDir = temp.newFolder(); + + properties = new Properties(); + properties.setProperty("sonar.path.home", homeDir.getAbsolutePath()); + } + + @Test + public void verifyProps_set_dir_path_absolute_based_on_home_dir_and_default_names_when_no_property() { + Props props = new Props(properties); + AppFileSystem underTest = new AppFileSystem(props); + + underTest.verifyProps(); + + assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_DATA)).isEqualTo(new File(homeDir, DEFAULT_DATA_DIR_NAME).getAbsolutePath()); + assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_WEB)).isEqualTo(new File(homeDir, DEFAULT_WEB_DIR_NAME).getAbsolutePath()); + assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_LOGS)).isEqualTo(new File(homeDir, DEFAULT_LOGS_DIR_NAME).getAbsolutePath()); + assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_TEMP)).isEqualTo(new File(homeDir, DEFAULT_TEMP_DIR_NAME).getAbsolutePath()); + } + + @Test + public void verifyProps_can_be_called_multiple_times() { + AppFileSystem underTest = new AppFileSystem(new Props(properties)); + + underTest.verifyProps(); + underTest.verifyProps(); + } + + @Test + public void reset_throws_ISE_if_verifyProps_not_called_first() throws Exception { + AppFileSystem underTest = new AppFileSystem(new Props(properties)); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("method verifyProps must be called first"); + + underTest.reset(); + } + + @Test + public void verifyProps_makes_dir_path_absolute_based_on_home_dir_when_relative() throws Exception { + properties.setProperty(PROPERTY_SONAR_PATH_WEB, NON_DEFAULT_WEB_DIR_NAME); + properties.setProperty(PROPERTY_SONAR_PATH_DATA, NON_DEFAULT_DATA_DIR_NAME); + properties.setProperty(PROPERTY_SONAR_PATH_LOGS, NON_DEFAULT_LOGS_DIR_NAME); + properties.setProperty(PROPERTY_SONAR_PATH_TEMP, NON_DEFAULT_TEMP_DIR_NAME); + + Props props = new Props(properties); + AppFileSystem underTest = new AppFileSystem(props); + + underTest.verifyProps(); + + assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_DATA)).isEqualTo(new File(homeDir, NON_DEFAULT_DATA_DIR_NAME).getAbsolutePath()); + assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_WEB)).isEqualTo(new File(homeDir, NON_DEFAULT_WEB_DIR_NAME).getAbsolutePath()); + assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_LOGS)).isEqualTo(new File(homeDir, NON_DEFAULT_LOGS_DIR_NAME).getAbsolutePath()); + assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_TEMP)).isEqualTo(new File(homeDir, NON_DEFAULT_TEMP_DIR_NAME).getAbsolutePath()); + } + + @Test + public void reset_creates_dir_all_dirs_if_they_don_t_exist() throws Exception { + AppFileSystem underTest = new AppFileSystem(new Props(properties)); + + underTest.verifyProps(); + + File dataDir = new File(homeDir, DEFAULT_DATA_DIR_NAME); + File webDir = new File(homeDir, DEFAULT_WEB_DIR_NAME); + File logsDir = new File(homeDir, DEFAULT_LOGS_DIR_NAME); + File tempDir = new File(homeDir, DEFAULT_TEMP_DIR_NAME); + assertThat(dataDir).doesNotExist(); + assertThat(webDir).doesNotExist(); + assertThat(logsDir).doesNotExist(); + assertThat(tempDir).doesNotExist(); + + underTest.reset(); + + assertThat(dataDir).exists().isDirectory(); + assertThat(webDir).exists().isDirectory(); + assertThat(logsDir).exists().isDirectory(); + assertThat(tempDir).exists().isDirectory(); + } + + @Test + public void reset_delete_temp_dir_if_already_exists() throws Exception { + File tempDir = new File(homeDir, DEFAULT_TEMP_DIR_NAME); + assertThat(tempDir.mkdir()).isTrue(); + File fileInTempDir = new File(tempDir, "someFile.txt"); + assertThat(fileInTempDir.createNewFile()).isTrue(); + + AppFileSystem underTest = new AppFileSystem(new Props(properties)); + underTest.verifyProps(); + underTest.reset(); + + assertThat(tempDir).exists(); + assertThat(fileInTempDir).doesNotExist(); + } + + @Test + public void reset_throws_ISE_if_data_dir_is_a_file() throws Exception { + resetThrowsISEIfDirIsAFile(PROPERTY_SONAR_PATH_DATA); + } + + @Test + public void reset_throws_ISE_if_web_dir_is_a_file() throws Exception { + resetThrowsISEIfDirIsAFile(PROPERTY_SONAR_PATH_WEB); + } + + @Test + public void reset_throws_ISE_if_logs_dir_is_a_file() throws Exception { + resetThrowsISEIfDirIsAFile(PROPERTY_SONAR_PATH_LOGS); + } + + @Test + public void reset_throws_ISE_if_temp_dir_is_a_file() throws Exception { + resetThrowsISEIfDirIsAFile(PROPERTY_SONAR_PATH_TEMP); + } + + private void resetThrowsISEIfDirIsAFile(String property) throws IOException { + File file = new File(homeDir, "zoom.store"); + assertThat(file.createNewFile()).isTrue(); + + properties.setProperty(property, "zoom.store"); + + AppFileSystem underTest = new AppFileSystem(new Props(properties)); + + underTest.verifyProps(); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Property '" + property + "' is not valid, not a directory: " + file.getAbsolutePath()); + + underTest.reset(); + } + + // @Test +// public void fail_if_required_directory_is_a_file() throws Exception { +// // <home>/data is missing +// FileUtils.forceMkdir(webDir); +// FileUtils.forceMkdir(logsDir); +// try { +// FileUtils.touch(dataDir); +// new PropsBuilder(new Properties(), jdbcSettings, homeDir).build(); +// fail(); +// } catch (IllegalStateException e) { +// assertThat(e.getMessage()).startsWith("Property 'sonar.path.data' is not valid, not a directory: " + dataDir.getAbsolutePath()); +// } +// } + +} diff --git a/sonar-application/src/test/java/org/sonar/application/PropsBuilderTest.java b/sonar-application/src/test/java/org/sonar/application/PropsBuilderTest.java index de5069940d2..ad5fb1ddb8a 100644 --- a/sonar-application/src/test/java/org/sonar/application/PropsBuilderTest.java +++ b/sonar-application/src/test/java/org/sonar/application/PropsBuilderTest.java @@ -19,6 +19,10 @@ */ package org.sonar.application; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Properties; import org.apache.commons.io.FileUtils; import org.junit.Before; import org.junit.Rule; @@ -26,13 +30,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonar.process.Props; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Properties; - import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; public class PropsBuilderTest { @@ -40,39 +38,21 @@ public class PropsBuilderTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); - File homeDir; - File dataDir; - File webDir; - File logsDir; - JdbcSettings jdbcSettings = mock(JdbcSettings.class); + private File homeDir; + private JdbcSettings jdbcSettings = mock(JdbcSettings.class); @Before public void before() throws IOException { homeDir = temp.newFolder(); - dataDir = new File(homeDir, "data"); - webDir = new File(homeDir, "web"); - logsDir = new File(homeDir, "logs"); } @Test public void build_props() throws Exception { - FileUtils.forceMkdir(dataDir); - FileUtils.forceMkdir(webDir); - FileUtils.forceMkdir(logsDir); Properties rawProperties = new Properties(); rawProperties.setProperty("foo", "bar"); Props props = new PropsBuilder(rawProperties, jdbcSettings, homeDir).build(); - assertThat(props.nonNullValueAsFile("sonar.path.logs")).isEqualTo(logsDir); - assertThat(props.nonNullValueAsFile("sonar.path.home")).isEqualTo(homeDir); - - // create <HOME>/temp - File tempDir = props.nonNullValueAsFile("sonar.path.temp"); - assertThat(tempDir).isDirectory().exists(); - assertThat(tempDir.getName()).isEqualTo("temp"); - assertThat(tempDir.getParentFile()).isEqualTo(homeDir); - assertThat(props.value("foo")).isEqualTo("bar"); assertThat(props.value("unknown")).isNull(); @@ -81,36 +61,8 @@ public class PropsBuilderTest { } @Test - public void create_missing_required_directory() throws Exception { - // <home>/data is missing - FileUtils.forceMkdir(webDir); - FileUtils.forceMkdir(logsDir); - - File dataDir = new File(homeDir, "data"); - new PropsBuilder(new Properties(), jdbcSettings, homeDir).build(); - assertThat(dataDir).isDirectory().exists(); - } - - @Test - public void fail_if_required_directory_is_a_file() throws Exception { - // <home>/data is missing - FileUtils.forceMkdir(webDir); - FileUtils.forceMkdir(logsDir); - try { - FileUtils.touch(dataDir); - new PropsBuilder(new Properties(), jdbcSettings, homeDir).build(); - fail(); - } catch (IllegalStateException e) { - assertThat(e.getMessage()).startsWith("Property 'sonar.path.data' is not valid, not a directory: " + dataDir.getAbsolutePath()); - } - } - - @Test public void load_properties_file_if_exists() throws Exception { FileUtils.write(new File(homeDir, "conf/sonar.properties"), "sonar.jdbc.username=angela\nsonar.origin=file"); - FileUtils.forceMkdir(dataDir); - FileUtils.forceMkdir(webDir); - FileUtils.forceMkdir(logsDir); Properties rawProperties = new Properties(); rawProperties.setProperty("sonar.origin", "raw"); @@ -132,10 +84,6 @@ public class PropsBuilderTest { @Test public void do_not_load_properties_file_if_not_exists() throws Exception { - FileUtils.forceMkdir(dataDir); - FileUtils.forceMkdir(webDir); - FileUtils.forceMkdir(logsDir); - Properties rawProperties = new Properties(); rawProperties.setProperty("sonar.origin", "raw"); Props props = new PropsBuilder(rawProperties, jdbcSettings, homeDir).build(); |