aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-11-21 16:00:19 +0100
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-11-24 09:23:58 +0100
commit5c4429e7a3e347437e2e8901d4e85a1c56dde66b (patch)
tree79050f0ced8b5fe60f6283066032e12cf3984eb3
parent3f8313ee989a20ce063a204d44a16ff805d8cf76 (diff)
downloadsonarqube-5c4429e7a3e347437e2e8901d4e85a1c56dde66b.tar.gz
sonarqube-5c4429e7a3e347437e2e8901d4e85a1c56dde66b.zip
SONAR-10104 async webhooks with full in-memory implementation
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java8
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java8
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecution.java31
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java24
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java59
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java46
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionModule.java31
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/async/package-info.java23
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/util/AbstractStoppableExecutorService.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java11
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java58
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java68
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java85
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/webhook/WebHooksImplTest.java)6
16 files changed, 454 insertions, 16 deletions
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
index 9bcaaad2909..febc63da76f 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
@@ -78,6 +78,7 @@ import org.sonar.process.NetworkUtilsImpl;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
import org.sonar.process.logging.LogbackHelper;
+import org.sonar.server.async.AsyncExecutionModule;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.component.index.ComponentIndexer;
import org.sonar.server.computation.task.projectanalysis.ProjectAnalysisTaskModule;
@@ -153,6 +154,7 @@ import org.sonar.server.user.index.UserIndexer;
import org.sonar.server.util.OkHttpClientProvider;
import org.sonar.server.view.index.ViewIndex;
import org.sonar.server.view.index.ViewIndexer;
+import org.sonar.server.webhook.WebhookModule;
import org.sonarqube.ws.Rules;
import static java.util.Objects.requireNonNull;
@@ -309,7 +311,8 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
ServerIdManager.class,
UriReader.class,
ServerImpl.class,
- DefaultOrganizationProviderImpl.class);
+ DefaultOrganizationProviderImpl.class,
+ AsyncExecutionModule.class);
}
private static void populateLevel4(ComponentContainer container, Props props) {
@@ -422,6 +425,9 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
InternalPropertiesImpl.class,
ProjectConfigurationFactory.class,
+ // webhooks
+ WebhookModule.class,
+
// cleaning
CeCleaningModule.class);
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
index 8c94f57d8e8..0793ae11024 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
@@ -91,7 +91,7 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getComponentAdapters())
.hasSize(
CONTAINER_ITSELF
- + 76 // level 4
+ + 77 // level 4
+ 6 // content of CeConfigurationModule
+ 4 // content of CeQueueModule
+ 4 // content of CeHttpModule
@@ -100,16 +100,18 @@ public class ComputeEngineContainerImplTest {
+ 7 // content of CeTaskProcessorModule
+ 4 // content of ReportAnalysisFailureNotificationModule
+ 3 // CeCleaningModule + its content
+ + 4 // WebhookModule
+ 1 // CeDistributedInformation
);
assertThat(picoContainer.getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
- + 5 // level 3
+ + 6 // level 3
+ + 2 // AsyncExecutionModule
);
assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
- + 13 // MigrationConfigurationModule
+ 17 // level 2
+ + 13 // MigrationConfigurationModule
);
assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecution.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecution.java
new file mode 100644
index 00000000000..f98d5824d72
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecution.java
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.async;
+
+public interface AsyncExecution {
+ /**
+ * Add the specified {@link Runnable} in queue for asynchronous processing.
+ *
+ * This method returns instantly and {@code r} is executed in another thread.
+ *
+ * @throws NullPointerException if r is {@code null}
+ */
+ void addToQueue(Runnable r);
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java
new file mode 100644
index 00000000000..bade9978b2c
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.async;
+
+public interface AsyncExecutionExecutorService {
+ void addToQueue(Runnable r);
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java
new file mode 100644
index 00000000000..60c87fc9fa6
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.async;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.server.util.AbstractStoppableExecutorService;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+public class AsyncExecutionExecutorServiceImpl
+ extends AbstractStoppableExecutorService<ExecutorService>
+ implements AsyncExecutionExecutorService {
+ private static final Logger LOG = Loggers.get(AsyncExecutionExecutorServiceImpl.class);
+
+ private static final int MIN_THREAD_COUNT = 1;
+ private static final int MAX_THREAD_COUNT = 10;
+ private static final int MAX_QUEUE_SIZE = Integer.MAX_VALUE;
+ private static final long KEEP_ALIVE_TIME_IN_MILLISECONDS = 0L;
+
+ public AsyncExecutionExecutorServiceImpl() {
+ super(
+ new ThreadPoolExecutor(
+ MIN_THREAD_COUNT, MAX_THREAD_COUNT,
+ KEEP_ALIVE_TIME_IN_MILLISECONDS, MILLISECONDS,
+ new LinkedBlockingQueue<>(MAX_QUEUE_SIZE),
+ new ThreadFactoryBuilder()
+ .setDaemon(false)
+ .setNameFormat("SQ_async-%d")
+ .setUncaughtExceptionHandler(((t, e) -> LOG.error("Thread " + t + " failed unexpectedly", e)))
+ .build()));
+ }
+
+ @Override
+ public void addToQueue(Runnable r) {
+ this.submit(r);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java
new file mode 100644
index 00000000000..d730bc309bc
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.async;
+
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static java.util.Objects.requireNonNull;
+
+public class AsyncExecutionImpl implements AsyncExecution {
+ private static final Logger LOG = Loggers.get(AsyncExecutionImpl.class);
+ private final AsyncExecutionExecutorService executorService;
+
+ public AsyncExecutionImpl(AsyncExecutionExecutorService executorService) {
+ this.executorService = executorService;
+ }
+
+ @Override
+ public void addToQueue(Runnable r) {
+ requireNonNull(r);
+ executorService.addToQueue(() -> {
+ try {
+ r.run();
+ } catch (Exception e) {
+ LOG.error("Asynchronous task failed", e);
+ }
+ });
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionModule.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionModule.java
new file mode 100644
index 00000000000..1e5673c45ee
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionModule.java
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.async;
+
+import org.sonar.core.platform.Module;
+
+public class AsyncExecutionModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(
+ AsyncExecutionExecutorServiceImpl.class,
+ AsyncExecutionImpl.class);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/async/package-info.java
new file mode 100644
index 00000000000..0934fefe908
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/async/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.async;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
index 6c49120776f..88fc743e11e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
@@ -122,7 +122,6 @@ import org.sonar.server.computation.task.step.ComputationStepExecutor;
import org.sonar.server.computation.task.step.ComputationSteps;
import org.sonar.server.computation.taskprocessor.MutableTaskResultHolderImpl;
import org.sonar.server.view.index.ViewIndex;
-import org.sonar.server.webhook.WebhookModule;
public final class ProjectAnalysisTaskContainerPopulator implements ContainerPopulator<TaskContainer> {
private static final ReportAnalysisComponentProvider[] NO_REPORT_ANALYSIS_COMPONENT_PROVIDERS = new ReportAnalysisComponentProvider[0];
@@ -272,7 +271,6 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
SmallChangesetQualityGateSpecialCase.class,
// webhooks
- WebhookModule.class,
WebhookPostTask.class);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
index d7c3a3753b6..9c4ab3de720 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
@@ -21,6 +21,7 @@ package org.sonar.server.platform.platformlevel;
import org.sonar.api.utils.UriReader;
import org.sonar.core.util.DefaultHttpDownloader;
+import org.sonar.server.async.AsyncExecutionModule;
import org.sonar.server.organization.DefaultOrganizationProviderImpl;
import org.sonar.server.organization.OrganizationFlagsImpl;
import org.sonar.server.platform.ServerIdManager;
@@ -47,6 +48,7 @@ public class PlatformLevel3 extends PlatformLevel {
UriReader.class,
DefaultHttpDownloader.class,
DefaultOrganizationProviderImpl.class,
- OrganizationFlagsImpl.class);
+ OrganizationFlagsImpl.class,
+ AsyncExecutionModule.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/AbstractStoppableExecutorService.java b/server/sonar-server/src/main/java/org/sonar/server/util/AbstractStoppableExecutorService.java
index 5a926a55ca5..a3e331846fc 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/util/AbstractStoppableExecutorService.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/util/AbstractStoppableExecutorService.java
@@ -36,10 +36,10 @@ import static java.lang.String.format;
* Abstract implementation of StoppableExecutorService that implements the
* stop() method and delegates all methods to the provided ExecutorService instance.
*/
-public abstract class AbstractStoppableExecutorService<T extends ExecutorService> implements StoppableExecutorService {
- protected final T delegate;
+public abstract class AbstractStoppableExecutorService<D extends ExecutorService> implements StoppableExecutorService {
+ protected final D delegate;
- public AbstractStoppableExecutorService(T delegate) {
+ public AbstractStoppableExecutorService(D delegate) {
this.delegate = delegate;
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java
index b839d8c43fe..8554484fe5b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java
@@ -30,6 +30,7 @@ import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.config.WebhookProperties;
import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.server.async.AsyncExecution;
import static java.lang.String.format;
import static org.sonar.core.config.WebhookProperties.MAX_WEBHOOKS_PER_TYPE;
@@ -41,10 +42,12 @@ public class WebHooksImpl implements WebHooks {
private final WebhookCaller caller;
private final WebhookDeliveryStorage deliveryStorage;
+ private final AsyncExecution asyncExecution;
- public WebHooksImpl(WebhookCaller caller, WebhookDeliveryStorage deliveryStorage) {
+ public WebHooksImpl(WebhookCaller caller, WebhookDeliveryStorage deliveryStorage, AsyncExecution asyncExecution) {
this.caller = caller;
this.deliveryStorage = deliveryStorage;
+ this.asyncExecution = asyncExecution;
}
@Override
@@ -88,12 +91,12 @@ public class WebHooksImpl implements WebHooks {
}
WebhookPayload payload = payloadSupplier.get();
- webhooks.forEach(webhook -> {
+ webhooks.forEach(webhook -> asyncExecution.addToQueue(() -> {
WebhookDelivery delivery = caller.call(webhook, payload);
log(delivery);
deliveryStorage.persist(delivery);
- });
- deliveryStorage.purge(analysis.getProjectUuid());
+ }));
+ asyncExecution.addToQueue(() -> deliveryStorage.purge(analysis.getProjectUuid()));
}
private static void log(WebhookDelivery delivery) {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java
new file mode 100644
index 00000000000..f8e0976bbe0
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.async;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AsyncExecutionExecutorServiceImplTest {
+ private AsyncExecutionExecutorServiceImpl underTest = new AsyncExecutionExecutorServiceImpl();
+
+ @Test
+ public void submit_executes_runnable_in_another_thread() {
+ try (SlowRunnable slowRunnable = new SlowRunnable()) {
+ underTest.submit(slowRunnable);
+ assertThat(slowRunnable.executed).isFalse();
+ }
+ }
+
+ private static final class SlowRunnable implements Runnable, AutoCloseable {
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private volatile boolean executed = false;
+
+ @Override
+ public void run() {
+ try {
+ latch.await(30, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ executed = true;
+ }
+
+ @Override
+ public void close() {
+ latch.countDown();
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java
new file mode 100644
index 00000000000..f98001c663e
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.async;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AsyncExecutionImplTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ private AsyncExecutionExecutorService synchronousExecutorService = Runnable::run;
+ private AsyncExecutionImpl underTest = new AsyncExecutionImpl(synchronousExecutorService);
+
+ @Test
+ public void addToQueue_fails_with_NPE_if_Runnable_is_null() {
+ expectedException.expect(NullPointerException.class);
+
+ underTest.addToQueue(null);
+ }
+
+ @Test
+ public void addToQueue_submits_runnable_to_executorService_which_does_not_fail_if_Runnable_argument_throws_exception() {
+ underTest.addToQueue(() -> {
+ throw new RuntimeException("Faking an exception thrown by Runnable argument");
+ });
+
+ assertThat(logTester.logs()).hasSize(1);
+ assertThat(logTester.logs(LoggerLevel.ERROR)).containsOnly("Asynchronous task failed");
+ }
+
+ @Test
+ public void addToQueue_submits_runnable_that_fails_if_Runnable_argument_throws_Error() {
+ Error expected = new Error("Faking an exception thrown by Runnable argument");
+ Runnable runnable = () -> {
+ throw expected;
+ };
+
+ expectedException.expect(Error.class);
+ expectedException.expectMessage(expected.getMessage());
+
+ underTest.addToQueue(runnable);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java
new file mode 100644
index 00000000000..5e0bfa22f40
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java
@@ -0,0 +1,85 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.webhook;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.async.AsyncExecution;
+
+import static java.util.Objects.requireNonNull;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class AsynchronousWebHooksImplTest {
+ private static final long NOW = 1_500_000_000_000L;
+ private static final String PROJECT_UUID = "P1_UUID";
+
+ private final MapSettings settings = new MapSettings();
+ private final TestWebhookCaller caller = new TestWebhookCaller();
+ private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
+ private final WebhookPayload mock = mock(WebhookPayload.class);
+ private final RecordingAsyncExecution asyncExecution = new RecordingAsyncExecution();
+
+ private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, asyncExecution);
+
+ @Test
+ public void send_global_webhooks() {
+ settings.setProperty("sonar.webhooks.global", "1,2");
+ settings.setProperty("sonar.webhooks.global.1.name", "First");
+ settings.setProperty("sonar.webhooks.global.1.url", "http://url1");
+ settings.setProperty("sonar.webhooks.global.2.name", "Second");
+ settings.setProperty("sonar.webhooks.global.2.url", "http://url2");
+ caller.enqueueSuccess(NOW, 200, 1_234);
+ caller.enqueueFailure(NOW, new IOException("Fail to connect"));
+
+ underTest.sendProjectAnalysisUpdate(settings.asConfig(), new WebHooks.Analysis(PROJECT_UUID, "1", "#1"), () -> mock);
+
+ assertThat(caller.countSent()).isZero();
+ verifyZeroInteractions(deliveryStorage);
+
+ asyncExecution.executeRecorded();
+
+ assertThat(caller.countSent()).isEqualTo(2);
+ verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
+ verify(deliveryStorage).purge(PROJECT_UUID);
+ }
+
+ private static class RecordingAsyncExecution implements AsyncExecution {
+ private final List<Runnable> runnableList = new ArrayList<>();
+
+ @Override
+ public void addToQueue(Runnable r) {
+ runnableList.add(requireNonNull(r));
+ }
+
+ public void executeRecorded() {
+ ArrayList<Runnable> runnables = new ArrayList<>(runnableList);
+ runnableList.clear();
+ runnables.forEach(Runnable::run);
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebHooksImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java
index abc72c3a316..17fc6de7046 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebHooksImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java
@@ -27,6 +27,7 @@ import org.junit.Test;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.server.async.AsyncExecution;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
@@ -35,7 +36,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
-public class WebHooksImplTest {
+public class SynchronousWebHooksImplTest {
private static final long NOW = 1_500_000_000_000L;
private static final String PROJECT_UUID = "P1_UUID";
@@ -47,7 +48,8 @@ public class WebHooksImplTest {
private final TestWebhookCaller caller = new TestWebhookCaller();
private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
private final WebhookPayload mock = mock(WebhookPayload.class);
- private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage);
+ private final AsyncExecution synchronousAsyncExecution = Runnable::run;
+ private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, synchronousAsyncExecution);
@Test
public void isEnabled_returns_false_if_no_webHoolds() {