]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3126 Add check for same server ID when launching a batch
authorFabrice Bellingard <bellingard@gmail.com>
Fri, 18 May 2012 09:46:45 +0000 (11:46 +0200)
committerFabrice Bellingard <bellingard@gmail.com>
Fri, 18 May 2012 09:47:50 +0000 (11:47 +0200)
=> Stop the Sonar Analysis when the Sonar batch and the Sonar server
   are not using the same DB

sonar-batch/src/main/java/org/sonar/batch/RemoteServerMetadata.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibility.java
sonar-batch/src/test/java/org/sonar/batch/RemoteServerMetadataTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/bootstrap/DatabaseBatchCompatibilityTest.java
sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java
sonar-core/src/test/java/org/sonar/core/persistence/DatabaseVersionTest.java
sonar-core/src/test/resources/org/sonar/core/persistence/DatabaseVersionTest/getSonarCoreId.xml [new file with mode: 0644]

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 (file)
index 0000000..c4202a5
--- /dev/null
@@ -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;
+  }
+
+}
index ced7e8a4d34bb3703092b0efbbf1ec514ad895f4..c987c4d09c0e86544ad3c38ceba263f76f5837e7 100644 (file)
@@ -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);
index 683bcda5d1d91a4b323e58c4b1d8816b56272508..21a149589aada5b84e621139818f3d18d713be2e 100644 (file)
 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 (file)
index 0000000..5a99dbf
--- /dev/null
@@ -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"));
+  }
+
+}
index 2059313776a12d3a4bda485fef9940ef73e1a033..4c02392148735f538be95658cdc990c50f83d80e 100644 (file)
  */
 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
   }
 }
index 43476f3f6cff0e174571159c44d5c38b58d819df..fc6290e80a417ad7ef1424728b35580ce5073204 100644 (file)
@@ -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);
   }
index 9dbee6dbe21410ad57cd2cfa7df68ba3227c6939..1ec1e6b84aa8ccff1735317901c0db8d02952785 100644 (file)
@@ -45,6 +45,15 @@ public class DatabaseVersionTest extends DaoTestCase {
     assertThat(version, nullValue());
   }
 
+  @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));
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 (file)
index 0000000..44b1e8e
--- /dev/null
@@ -0,0 +1,3 @@
+<dataset>
+  <properties id="1" prop_key="sonar.core.id" resource_id="[null]" text_value="123456" user_id="[null]"/>
+</dataset>