diff options
8 files changed, 304 insertions, 16 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/RemoteServerMetadata.java b/sonar-batch/src/main/java/org/sonar/batch/RemoteServerMetadata.java new file mode 100644 index 00000000000..c4202a5e40b --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/RemoteServerMetadata.java @@ -0,0 +1,91 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch; + +import org.apache.commons.lang.StringUtils; +import org.codehaus.plexus.util.IOUtil; +import org.sonar.api.BatchComponent; +import org.sonar.api.platform.Server; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +public class RemoteServerMetadata implements BatchComponent { + + public static final int CONNECT_TIMEOUT_MILLISECONDS = 30000; + public static final int READ_TIMEOUT_MILLISECONDS = 60000; + + private String serverUrl; + + public RemoteServerMetadata(Server server) { + serverUrl = server.getURL(); + if (serverUrl.endsWith("/")) { + serverUrl = StringUtils.chop(serverUrl); + } + } + + public String getServerId() throws IOException { + String remoteServerInfo = remoteContent("/api/server"); + // don't use JSON utilities to extract ID from such a small string + String id = extractId(remoteServerInfo); + return id; + } + + protected String extractId(String remoteServerInfo) { + String partialId = StringUtils.substringAfter(remoteServerInfo, "\"id\":\""); + String id = StringUtils.substringBefore(partialId, "\""); + return id; + } + + protected String getUrlFor(String path) { + String fullUrl = serverUrl + path; + return fullUrl; + } + + protected String remoteContent(String path) throws IOException { + String fullUrl = getUrlFor(path); + HttpURLConnection conn = getConnection(fullUrl, "GET"); + InputStream input = (InputStream) conn.getContent(); + try { + int statusCode = conn.getResponseCode(); + if (statusCode != HttpURLConnection.HTTP_OK) { + throw new IOException("Status returned by url : '" + fullUrl + "' is invalid : " + statusCode); + } + return IOUtil.toString(input); + + } finally { + IOUtil.close(input); + conn.disconnect(); + } + } + + static HttpURLConnection getConnection(String url, String method) throws IOException { + URL page = new URL(url); + HttpURLConnection conn = (HttpURLConnection) page.openConnection(); + conn.setConnectTimeout(CONNECT_TIMEOUT_MILLISECONDS); + conn.setReadTimeout(READ_TIMEOUT_MILLISECONDS); + conn.setRequestMethod(method); + conn.connect(); + return conn; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java index ced7e8a4d34..c987c4d09c0 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java @@ -24,15 +24,16 @@ import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.utils.HttpDownloader; import org.sonar.batch.FakeMavenPluginExecutor; import org.sonar.batch.MavenPluginExecutor; +import org.sonar.batch.RemoteServerMetadata; import org.sonar.batch.ServerMetadata; -import org.sonar.batch.config.BatchSettings; import org.sonar.batch.config.BatchDatabaseSettingsLoader; +import org.sonar.batch.config.BatchSettings; +import org.sonar.core.persistence.DaoUtils; import org.sonar.core.persistence.DatabaseVersion; +import org.sonar.core.persistence.MyBatis; import org.sonar.jpa.session.DatabaseSessionProvider; import org.sonar.jpa.session.DefaultDatabaseConnector; import org.sonar.jpa.session.ThreadLocalDatabaseSessionFactory; -import org.sonar.core.persistence.DaoUtils; -import org.sonar.core.persistence.MyBatis; import java.net.URLClassLoader; @@ -65,6 +66,7 @@ public class BootstrapModule extends Module { // set as the current context classloader for hibernate, else it does not find the JDBC driver. Thread.currentThread().setContextClassLoader(bootstrapClassLoader); + addCoreSingleton(RemoteServerMetadata.class); // mybatis addCoreSingleton(BatchDatabase.class); addCoreSingleton(MyBatis.class); diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibility.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibility.java index 683bcda5d1d..21a149589aa 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibility.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibility.java @@ -20,10 +20,16 @@ package org.sonar.batch.bootstrap; import org.sonar.api.BatchComponent; +import org.sonar.api.config.Settings; +import org.sonar.api.database.DatabaseProperties; import org.sonar.api.platform.Server; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.RemoteServerMetadata; import org.sonar.core.persistence.BadDatabaseVersion; import org.sonar.core.persistence.DatabaseVersion; +import java.io.IOException; + /** * Detects if database is not up-to-date with the version required by the batch. */ @@ -31,13 +37,44 @@ public class DatabaseBatchCompatibility implements BatchComponent { private DatabaseVersion version; private Server server; + private Settings settings; + private RemoteServerMetadata remoteServer; - public DatabaseBatchCompatibility(DatabaseVersion version, Server server) { + public DatabaseBatchCompatibility(DatabaseVersion version, Server server, RemoteServerMetadata remoteServer, Settings settings) { this.version = version; this.server = server; + this.settings = settings; + this.remoteServer = remoteServer; } public void start() { + checkCorrectServerId(); + checkDatabaseStatus(); + } + + private void checkCorrectServerId() { + String remoteServerId = null; + try { + remoteServerId = remoteServer.getServerId(); + } catch (IOException e) { + throw new SonarException("Impossible to get the ID of the remote server: " + server.getURL(), e); + } + + if (!version.getSonarCoreId().equals(remoteServerId)) { + StringBuilder message = new StringBuilder("The current batch process and the configured remote server do not share the same DB configuration.\n"); + message.append("\t- Batch side: "); + message.append(settings.getString(DatabaseProperties.PROP_URL)); + message.append(" ("); + String userName = settings.getString(DatabaseProperties.PROP_USER); + message.append(userName == null ? "sonar" : userName); + message.append(" / *****)\n\t- Server side: check the configuration at "); + message.append(server.getURL()); + message.append("/system\n"); + throw new BadDatabaseVersion(message.toString()); + } + } + + private void checkDatabaseStatus() { DatabaseVersion.Status status = version.getStatus(); if (status == DatabaseVersion.Status.REQUIRES_DOWNGRADE) { throw new BadDatabaseVersion("Database relates to a more recent version of Sonar. Please check your settings (JDBC settings, version of Maven plugin)"); diff --git a/sonar-batch/src/test/java/org/sonar/batch/RemoteServerMetadataTest.java b/sonar-batch/src/test/java/org/sonar/batch/RemoteServerMetadataTest.java new file mode 100644 index 00000000000..5a99dbf5ce1 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/RemoteServerMetadataTest.java @@ -0,0 +1,62 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.platform.Server; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RemoteServerMetadataTest { + + private RemoteServerMetadata remoteServerMetadata; + + @Before + public void init() throws Exception { + Server server = mock(Server.class); + when(server.getURL()).thenReturn("http://localhost:8080"); + + remoteServerMetadata = new RemoteServerMetadata(server); + } + + @Test + public void shouldExtractId() throws Exception { + assertThat(remoteServerMetadata.extractId("{\"id\":\"123456\",\"version\":\"3.1\",\"status\":\"UP\"}"), is("123456")); + } + + @Test + public void shouldGiveUrlForPath() throws Exception { + assertThat(remoteServerMetadata.getUrlFor("/api/server"), is("http://localhost:8080/api/server")); + } + + @Test + public void handleLastSlash() throws Exception { + Server server = mock(Server.class); + when(server.getURL()).thenReturn("http://localhost:8080/"); + + remoteServerMetadata = new RemoteServerMetadata(server); + assertThat(remoteServerMetadata.getUrlFor("/api/server"), is("http://localhost:8080/api/server")); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibilityTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibilityTest.java index 2059313776a..4c023921487 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibilityTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibilityTest.java @@ -19,39 +19,109 @@ */ package org.sonar.batch.bootstrap; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.config.Settings; +import org.sonar.api.database.DatabaseProperties; import org.sonar.api.platform.Server; -import org.sonar.batch.ServerMetadata; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.RemoteServerMetadata; import org.sonar.core.persistence.BadDatabaseVersion; import org.sonar.core.persistence.DatabaseVersion; +import java.io.IOException; + import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class DatabaseBatchCompatibilityTest { - private Server server = new ServerMetadata(new Settings()); + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DatabaseVersion databaseVersion; + private Server server; + private Settings settings; + private RemoteServerMetadata remoteServerMetadata; + + @Before + public void init() throws Exception { + databaseVersion = mock(DatabaseVersion.class); + when(databaseVersion.getSonarCoreId()).thenReturn("123456"); + + server = mock(Server.class); + when(server.getURL()).thenReturn("http://localhost:9000"); - @Test(expected = BadDatabaseVersion.class) + settings = new Settings(); + settings.setProperty(DatabaseProperties.PROP_URL, "jdbc:postgresql://localhost/foo"); + settings.setProperty(DatabaseProperties.PROP_USER, "bar"); + + remoteServerMetadata = mock(RemoteServerMetadata.class); + when(remoteServerMetadata.getServerId()).thenReturn("123456"); + } + + @Test public void shouldFailIfRequiresDowngrade() { - DatabaseVersion version = mock(DatabaseVersion.class); - when(version.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_DOWNGRADE); - new DatabaseBatchCompatibility(version, server).start(); + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_DOWNGRADE); + + thrown.expect(BadDatabaseVersion.class); + thrown.expectMessage("Database relates to a more recent version of Sonar. Please check your settings (JDBC settings, version of Maven plugin)"); + + new DatabaseBatchCompatibility(databaseVersion, server, remoteServerMetadata, settings).start(); } - @Test(expected = BadDatabaseVersion.class) + @Test public void shouldFailIfRequiresUpgrade() { + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); + + thrown.expect(BadDatabaseVersion.class); + thrown.expectMessage("Database must be upgraded."); + + new DatabaseBatchCompatibility(databaseVersion, server, remoteServerMetadata, settings).start(); + } + + @Test + public void shouldFailIfNotSameServerId() throws Exception { DatabaseVersion version = mock(DatabaseVersion.class); - when(version.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); - new DatabaseBatchCompatibility(version, server).start(); + when(version.getSonarCoreId()).thenReturn("1111111"); + + thrown.expect(BadDatabaseVersion.class); + thrown.expectMessage("The current batch process and the configured remote server do not share the same DB configuration."); + thrown.expectMessage("- Batch side: jdbc:postgresql://localhost/foo (bar / *****)"); + thrown.expectMessage("- Server side: check the configuration at http://localhost:9000/system"); + + new DatabaseBatchCompatibility(version, server, remoteServerMetadata, settings).start(); } @Test - public void shouldDoNothingIfUpToDate() { + public void shouldUseDefaultUserNameWhenFaillingIfNotSameServerIdAndNoUserNameFound() throws Exception { DatabaseVersion version = mock(DatabaseVersion.class); - when(version.getStatus()).thenReturn(DatabaseVersion.Status.UP_TO_DATE); - new DatabaseBatchCompatibility(version, server).start(); + when(version.getSonarCoreId()).thenReturn("1111111"); + + settings.removeProperty(DatabaseProperties.PROP_USER); + + thrown.expect(BadDatabaseVersion.class); + thrown.expectMessage("- Batch side: jdbc:postgresql://localhost/foo (sonar / *****)"); + + new DatabaseBatchCompatibility(version, server, remoteServerMetadata, settings).start(); + } + + @Test + public void shouldFailIfCantGetServerId() throws Exception { + when(remoteServerMetadata.getServerId()).thenThrow(IOException.class); + + thrown.expect(SonarException.class); + thrown.expectMessage("Impossible to get the ID of the remote server: http://localhost:9000"); + + new DatabaseBatchCompatibility(mock(DatabaseVersion.class), server, remoteServerMetadata, settings).start(); + } + + @Test + public void shouldDoNothingIfUpToDate() { + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.UP_TO_DATE); + new DatabaseBatchCompatibility(databaseVersion, server, remoteServerMetadata, settings).start(); // no error } } diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java index 43476f3f6cf..fc6290e80a4 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java @@ -22,7 +22,10 @@ package org.sonar.core.persistence; import com.google.common.annotations.VisibleForTesting; import org.apache.ibatis.session.SqlSession; import org.sonar.api.BatchComponent; +import org.sonar.api.CoreProperties; import org.sonar.api.ServerComponent; +import org.sonar.core.properties.PropertiesMapper; +import org.sonar.core.properties.PropertyDto; import java.util.Collections; import java.util.List; @@ -63,6 +66,17 @@ public class DatabaseVersion implements BatchComponent, ServerComponent { } } + public String getSonarCoreId() { + SqlSession session = mybatis.openSession(); + try { + PropertyDto serverIdProperty = session.getMapper(PropertiesMapper.class).selectByKey(new PropertyDto().setKey(CoreProperties.SERVER_ID)); + // this property can't be NULL + return serverIdProperty.getValue(); + } finally { + MyBatis.closeQuietly(session); + } + } + public Status getStatus() { return getStatus(getVersion(), LAST_VERSION); } diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseVersionTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseVersionTest.java index 9dbee6dbe21..1ec1e6b84aa 100644 --- a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseVersionTest.java +++ b/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseVersionTest.java @@ -46,6 +46,15 @@ public class DatabaseVersionTest extends DaoTestCase { } @Test + public void getSonarCoreId() { + setupData("getSonarCoreId"); + + String sonarCoreId = new DatabaseVersion(getMyBatis()).getSonarCoreId(); + + assertThat(sonarCoreId, is("123456")); + } + + @Test public void getStatus() { assertThat(DatabaseVersion.getStatus(null, 150), is(DatabaseVersion.Status.FRESH_INSTALL)); assertThat(DatabaseVersion.getStatus(123, 150), is(DatabaseVersion.Status.REQUIRES_UPGRADE)); diff --git a/sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getSonarCoreId.xml b/sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getSonarCoreId.xml new file mode 100644 index 00000000000..44b1e8eb5be --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getSonarCoreId.xml @@ -0,0 +1,3 @@ +<dataset> + <properties id="1" prop_key="sonar.core.id" resource_id="[null]" text_value="123456" user_id="[null]"/> +</dataset> |