]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4488 Increase timeout for /batch_bootstrap/db
authorJulien HENRY <julien.henry@sonarsource.com>
Thu, 11 Jul 2013 10:22:50 +0000 (12:22 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Thu, 11 Jul 2013 10:57:52 +0000 (12:57 +0200)
sonar-batch/src/main/java/org/sonar/batch/bootstrap/DryRunDatabase.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/DryRunDatabaseTest.java
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java
sonar-plugin-api/src/test/java/org/sonar/api/utils/HttpDownloaderTest.java

index 0f73212e3376eb0b5f697895891e2d74be93f03a..e8326e916b1a59839fef54e43ff241f0b1b67bf7 100644 (file)
@@ -32,6 +32,7 @@ import org.sonar.api.utils.HttpDownloader.HttpException;
 import org.sonar.api.utils.SonarException;
 
 import java.io.File;
+import java.net.SocketTimeoutException;
 
 /**
  * @since 3.4
@@ -45,6 +46,8 @@ public class DryRunDatabase implements BatchComponent {
   private static final String USER = "sonar";
   private static final String PASSWORD = "sonar";
 
+  private static final int DEFAULT_DRY_RUN_READ_TIMEOUT = 60 * 1000;
+
   private final Settings settings;
   private final ServerClient server;
   private final TempDirectories tempDirectories;
@@ -59,14 +62,19 @@ public class DryRunDatabase implements BatchComponent {
     if (settings.getBoolean(CoreProperties.DRY_RUN)) {
       LOG.info("Dry run");
       File databaseFile = tempDirectories.getFile("", "dryrun.h2.db");
-      downloadDatabase(databaseFile);
+
+      // SONAR-4488 Allow to increase dryRun timeout
+      int readTimeout = settings.getInt(CoreProperties.DRY_RUN_READ_TIMEOUT);
+      readTimeout = (readTimeout == 0) ? DEFAULT_DRY_RUN_READ_TIMEOUT : readTimeout;
+
+      downloadDatabase(databaseFile, readTimeout);
 
       String databasePath = StringUtils.removeEnd(databaseFile.getAbsolutePath(), ".h2.db");
       replaceSettings(databasePath);
     }
   }
 
-  private void downloadDatabase(File toFile) {
+  private void downloadDatabase(File toFile, int readTimeout) {
     String projectKey = null;
     try {
       projectKey = settings.getString(CoreProperties.PROJECT_KEY_PROPERTY);
@@ -75,15 +83,22 @@ public class DryRunDatabase implements BatchComponent {
         projectKey = String.format("%s:%s", projectKey, branch);
       }
       if (StringUtils.isBlank(projectKey)) {
-        server.download("/batch_bootstrap/db", toFile);
+        server.download("/batch_bootstrap/db", toFile, readTimeout);
       } else {
-        server.download("/batch_bootstrap/db?project=" + projectKey, toFile);
+        server.download("/batch_bootstrap/db?project=" + projectKey, toFile, readTimeout);
       }
       LOG.debug("Dry Run database size: {}", FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(toFile)));
     } catch (SonarException e) {
       Throwable rootCause = Throwables.getRootCause(e);
+      if (rootCause instanceof SocketTimeoutException) {
+        // Pico will unwrap the first runtime exception
+        throw new SonarException(new SonarException(String.format("DryRun database read timed out after %s ms. You can try to increase read timeout with property -D"
+          + CoreProperties.DRY_RUN_READ_TIMEOUT,
+            readTimeout), e));
+      }
       if (projectKey != null && (rootCause instanceof HttpException) && (((HttpException) rootCause).getResponseCode() == 401)) {
-        throw new SonarException(String.format("You don't have access rights to project [%s]", projectKey), e);
+        // Pico will unwrap the first runtime exception
+        throw new SonarException(new SonarException(String.format("You don't have access rights to project [%s]", projectKey), e));
       }
       throw e;
     }
index 16028be1299bf679484c6158e2ae496991798198..b04bd67a360765619515f64b8bd93599ec7e464c 100644 (file)
@@ -58,8 +58,12 @@ public class ServerClient implements BatchComponent {
   }
 
   public void download(String pathStartingWithSlash, File toFile) {
+    download(pathStartingWithSlash, toFile, null);
+  }
+
+  public void download(String pathStartingWithSlash, File toFile, Integer readTimeoutMillis) {
     try {
-      InputSupplier<InputStream> inputSupplier = doRequest(pathStartingWithSlash);
+      InputSupplier<InputStream> inputSupplier = doRequest(pathStartingWithSlash, readTimeoutMillis);
       Files.copy(inputSupplier, toFile);
     } catch (HttpDownloader.HttpException he) {
       throw handleHttpException(he);
@@ -73,7 +77,11 @@ public class ServerClient implements BatchComponent {
   }
 
   public String request(String pathStartingWithSlash, boolean wrapHttpException) {
-    InputSupplier<InputStream> inputSupplier = doRequest(pathStartingWithSlash);
+    return request(pathStartingWithSlash, wrapHttpException, null);
+  }
+
+  public String request(String pathStartingWithSlash, boolean wrapHttpException, Integer timeoutMillis) {
+    InputSupplier<InputStream> inputSupplier = doRequest(pathStartingWithSlash, timeoutMillis);
     try {
       return IOUtils.toString(inputSupplier.getInput(), "UTF-8");
     } catch (HttpDownloader.HttpException e) {
@@ -83,7 +91,7 @@ public class ServerClient implements BatchComponent {
     }
   }
 
-  private InputSupplier<InputStream> doRequest(String pathStartingWithSlash) {
+  private InputSupplier<InputStream> doRequest(String pathStartingWithSlash, Integer timeoutMillis) {
     Preconditions.checkArgument(pathStartingWithSlash.startsWith("/"), "Path must start with slash /");
     String path = StringEscapeUtils.escapeHtml(pathStartingWithSlash);
 
@@ -91,9 +99,9 @@ public class ServerClient implements BatchComponent {
     try {
       InputSupplier<InputStream> inputSupplier;
       if (Strings.isNullOrEmpty(getLogin())) {
-        inputSupplier = downloader.newInputSupplier(uri);
+        inputSupplier = downloader.newInputSupplier(uri, timeoutMillis);
       } else {
-        inputSupplier = downloader.newInputSupplier(uri, getLogin(), getPassword());
+        inputSupplier = downloader.newInputSupplier(uri, getLogin(), getPassword(), timeoutMillis);
       }
       return inputSupplier;
     } catch (Exception e) {
index f19dbc663af1ed1d185ba4df2985cfb75af509e4..5e872ed01517c717183c4977a4bff02b2d8dd660 100644 (file)
@@ -32,6 +32,7 @@ import org.sonar.api.utils.HttpDownloader;
 import org.sonar.api.utils.SonarException;
 
 import java.io.File;
+import java.net.SocketTimeoutException;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.doThrow;
@@ -73,7 +74,15 @@ public class DryRunDatabaseTest {
   public void should_download_database() {
     new DryRunDatabase(settings, server, tempDirectories).start();
 
-    verify(server).download("/batch_bootstrap/db?project=group:project", databaseFile);
+    verify(server).download("/batch_bootstrap/db?project=group:project", databaseFile, 60000);
+  }
+
+  @Test
+  public void should_download_database_with_overriden_timeout() {
+    settings.setProperty(CoreProperties.DRY_RUN_READ_TIMEOUT, 80000);
+    new DryRunDatabase(settings, server, tempDirectories).start();
+
+    verify(server).download("/batch_bootstrap/db?project=group:project", databaseFile, 80000);
   }
 
   @Test
@@ -81,7 +90,7 @@ public class DryRunDatabaseTest {
     settings.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "mybranch");
     new DryRunDatabase(settings, server, tempDirectories).start();
 
-    verify(server).download("/batch_bootstrap/db?project=group:project:mybranch", databaseFile);
+    verify(server).download("/batch_bootstrap/db?project=group:project:mybranch", databaseFile, 60000);
   }
 
   @Test
@@ -97,7 +106,7 @@ public class DryRunDatabaseTest {
 
   @Test
   public void should_fail_on_invalid_role() {
-    doThrow(new SonarException(new HttpDownloader.HttpException(null, 401))).when(server).download("/batch_bootstrap/db?project=group:project", databaseFile);
+    doThrow(new SonarException(new HttpDownloader.HttpException(null, 401))).when(server).download("/batch_bootstrap/db?project=group:project", databaseFile, 60000);
 
     thrown.expect(SonarException.class);
     thrown.expectMessage("You don't have access rights to project [group:project]");
@@ -105,9 +114,19 @@ public class DryRunDatabaseTest {
     new DryRunDatabase(settings, server, tempDirectories).start();
   }
 
+  @Test
+  public void should_fail_on_read_timeout() {
+    doThrow(new SonarException(new SocketTimeoutException())).when(server).download("/batch_bootstrap/db?project=group:project", databaseFile, 60000);
+
+    thrown.expect(SonarException.class);
+    thrown.expectMessage("DryRun database read timed out after 60000 ms. You can try to increase read timeout with property -Dsonar.dryRun.readTimeout");
+
+    new DryRunDatabase(settings, server, tempDirectories).start();
+  }
+
   @Test
   public void should_fail() {
-    doThrow(new SonarException("BUG")).when(server).download("/batch_bootstrap/db?project=group:project", databaseFile);
+    doThrow(new SonarException("BUG")).when(server).download("/batch_bootstrap/db?project=group:project", databaseFile, 60000);
 
     thrown.expect(SonarException.class);
     thrown.expectMessage("BUG");
@@ -120,6 +139,6 @@ public class DryRunDatabaseTest {
     // on non-scan tasks
     settings.removeProperty(CoreProperties.PROJECT_KEY_PROPERTY);
     new DryRunDatabase(settings, server, tempDirectories).start();
-    verify(server).download("/batch_bootstrap/db", databaseFile);
+    verify(server).download("/batch_bootstrap/db", databaseFile, 60000);
   }
 }
index 1d0f2e8460afd3e2e12c390173e03f11a00efa43..e0f25637bcf1878b67097a879fb2632f846c7340 100644 (file)
@@ -400,4 +400,9 @@ public interface CoreProperties {
    */
   @Deprecated
   String CORE_COVERAGE_PLUGIN_PROPERTY = "sonar.core.codeCoveragePlugin";
+
+  /**
+   * @since 3.7
+   */
+  String DRY_RUN_READ_TIMEOUT = "sonar.dryRun.readTimeout";
 }
index cb64b82be9582fdcf3a2f8ea0009dfde2e98bbf2..deaf6d1fed06bfae7cfa611dc5d978992fb8b4cd 100644 (file)
@@ -37,6 +37,8 @@ import org.sonar.api.ServerComponent;
 import org.sonar.api.config.Settings;
 import org.sonar.api.platform.Server;
 
+import javax.annotation.Nullable;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -59,12 +61,23 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo
   public static final int TIMEOUT_MILLISECONDS = 20 * 1000;
 
   private final BaseHttpDownloader downloader;
+  private final Integer readTimeout;
 
   public HttpDownloader(Server server, Settings settings) {
+    this(server, settings, null);
+  }
+
+  public HttpDownloader(Server server, Settings settings, @Nullable Integer readTimeout) {
+    this.readTimeout = readTimeout;
     downloader = new BaseHttpDownloader(settings.getProperties(), server.getVersion());
   }
 
   public HttpDownloader(Settings settings) {
+    this(settings, null);
+  }
+
+  public HttpDownloader(Settings settings, @Nullable Integer readTimeout) {
+    this.readTimeout = readTimeout;
     downloader = new BaseHttpDownloader(settings.getProperties(), null);
   }
 
@@ -86,7 +99,7 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo
   @Override
   String readString(URI uri, Charset charset) {
     try {
-      return CharStreams.toString(CharStreams.newReaderSupplier(downloader.newInputSupplier(uri), charset));
+      return CharStreams.toString(CharStreams.newReaderSupplier(downloader.newInputSupplier(uri, this.readTimeout), charset));
     } catch (IOException e) {
       throw failToDownload(uri, e);
     }
@@ -98,7 +111,7 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo
 
   public byte[] download(URI uri) {
     try {
-      return ByteStreams.toByteArray(downloader.newInputSupplier(uri));
+      return ByteStreams.toByteArray(downloader.newInputSupplier(uri, this.readTimeout));
     } catch (IOException e) {
       throw failToDownload(uri, e);
     }
@@ -110,7 +123,7 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo
 
   public InputStream openStream(URI uri) {
     try {
-      return downloader.newInputSupplier(uri).getInput();
+      return downloader.newInputSupplier(uri, this.readTimeout).getInput();
     } catch (IOException e) {
       throw failToDownload(uri, e);
     }
@@ -118,7 +131,7 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo
 
   public void download(URI uri, File toFile) {
     try {
-      Files.copy(downloader.newInputSupplier(uri), toFile);
+      Files.copy(downloader.newInputSupplier(uri, this.readTimeout), toFile);
     } catch (IOException e) {
       FileUtils.deleteQuietly(toFile);
       throw failToDownload(uri, e);
@@ -193,11 +206,25 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo
     }
 
     public InputSupplier<InputStream> newInputSupplier(URI uri) {
-      return new HttpInputSupplier(uri, userAgent, null, null);
+      return new HttpInputSupplier(uri, userAgent, null, null, TIMEOUT_MILLISECONDS);
+    }
+
+    public InputSupplier<InputStream> newInputSupplier(URI uri, @Nullable Integer readTimeoutMillis) {
+      if (readTimeoutMillis != null) {
+        return new HttpInputSupplier(uri, userAgent, null, null, readTimeoutMillis);
+      }
+      return new HttpInputSupplier(uri, userAgent, null, null, TIMEOUT_MILLISECONDS);
     }
 
     public InputSupplier<InputStream> newInputSupplier(URI uri, String login, String password) {
-      return new HttpInputSupplier(uri, userAgent, login, password);
+      return new HttpInputSupplier(uri, userAgent, login, password, TIMEOUT_MILLISECONDS);
+    }
+
+    public InputSupplier<InputStream> newInputSupplier(URI uri, String login, String password, @Nullable Integer readTimeoutMillis) {
+      if (readTimeoutMillis != null) {
+        return new HttpInputSupplier(uri, userAgent, login, password, readTimeoutMillis);
+      }
+      return new HttpInputSupplier(uri, userAgent, login, password, TIMEOUT_MILLISECONDS);
     }
 
     private static class HttpInputSupplier implements InputSupplier<InputStream> {
@@ -205,12 +232,14 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo
       private final String password;
       private final URI uri;
       private final String userAgent;
+      private final int readTimeoutMillis;
 
-      HttpInputSupplier(URI uri, String userAgent, String login, String password) {
+      HttpInputSupplier(URI uri, String userAgent, String login, String password, int readTimeoutMillis) {
         this.uri = uri;
         this.userAgent = userAgent;
         this.login = login;
         this.password = password;
+        this.readTimeoutMillis = readTimeoutMillis;
       }
 
       public InputStream getInput() throws IOException {
@@ -222,7 +251,7 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo
           connection.setRequestProperty("Authorization", "Basic " + encoded);
         }
         connection.setConnectTimeout(TIMEOUT_MILLISECONDS);
-        connection.setReadTimeout(TIMEOUT_MILLISECONDS);
+        connection.setReadTimeout(readTimeoutMillis);
         connection.setUseCaches(true);
         connection.setInstanceFollowRedirects(true);
         connection.setRequestProperty("User-Agent", userAgent);
index 1074b651a07dbc1e371dbd1283cf9a37088a5bed..7a752ab52645f0d8da762ded5f486bd95d1a474d 100644 (file)
 package org.sonar.api.utils;
 
 import com.google.common.base.Charsets;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
@@ -39,6 +42,7 @@ import java.net.InetSocketAddress;
 import java.net.Proxy;
 import java.net.ProxySelector;
 import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Arrays;
@@ -54,6 +58,9 @@ public class HttpDownloaderTest {
   @Rule
   public TemporaryFolder temporaryFolder = new TemporaryFolder();
 
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   private static SocketConnection socketConnection;
   private static String baseUrl;
 
@@ -65,7 +72,15 @@ public class HttpDownloaderTest {
           if (req.getPath().getPath().contains("/redirect/")) {
             resp.setCode(303);
             resp.add("Location", "/");
-          } else {
+          }
+          else {
+            if (req.getPath().getPath().contains("/timeout/")) {
+              try {
+                Thread.sleep(1000);
+              } catch (InterruptedException e) {
+                e.printStackTrace();
+              }
+            }
             resp.getPrintStream().append("agent=" + req.getValues("User-Agent").get(0));
           }
         } catch (IOException e) {
@@ -101,6 +116,33 @@ public class HttpDownloaderTest {
     assertThat(text.length()).isGreaterThan(10);
   }
 
+  @Test
+  public void readStringWithDefaultTimeout() throws URISyntaxException {
+    String text = new HttpDownloader(new Settings()).readString(new URI(baseUrl + "/timeout/"), Charsets.UTF_8);
+    assertThat(text.length()).isGreaterThan(10);
+  }
+
+  @Test
+  public void readStringWithTimeout() throws URISyntaxException {
+    String text = new HttpDownloader(new Settings(), 2000).readString(new URI(baseUrl + "/timeout/"), Charsets.UTF_8);
+    assertThat(text.length()).isGreaterThan(10);
+
+    thrown.expect(new BaseMatcher<Exception>() {
+      @Override
+      public boolean matches(Object ex) {
+        // TODO Auto-generated method stub
+        return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
+      }
+
+      @Override
+      public void describeTo(Description arg0) {
+        // TODO Auto-generated method stub
+
+      }
+    });
+    new HttpDownloader(new Settings(), 100).readString(new URI(baseUrl + "/timeout/"), Charsets.UTF_8);
+  }
+
   @Test(expected = SonarException.class)
   public void failIfServerDown() throws URISyntaxException {
     // I hope that the port 1 is not used !