diff options
author | ssjenka <ssjenka@ops-slave-fedora25-1.internal.sonarsource.com> | 2017-11-03 16:01:25 +0100 |
---|---|---|
committer | ssjenka <ssjenka@ops-slave-fedora25-1.internal.sonarsource.com> | 2017-11-03 16:01:25 +0100 |
commit | 5326c7cc97db482803c11f06a45a7e497824765d (patch) | |
tree | e7ae79ec6cf0082a1586272b7fab8d531ae9b943 | |
parent | 46c24fe4dfa047d7b14c55fc376f502daa3d6050 (diff) | |
parent | 61d27599390f01279a25241daa5d361fcb362803 (diff) | |
download | sonarqube-5326c7cc97db482803c11f06a45a7e497824765d.tar.gz sonarqube-5326c7cc97db482803c11f06a45a7e497824765d.zip |
Automatic merge from branch-6.7
* origin/branch-6.7:
SONAR-9973 IT for report processing resilient to failure during start
SONAR-9973 TaskContainer now support errors during start of components
SONAR-9973 add IT ensuring resilience of Ce Workers to exceptions
SONAR-9973 ComponentContainer#stopComponent now never fail
22 files changed, 951 insertions, 105 deletions
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java index 96c2fe978cf..bce29650b9a 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java @@ -28,6 +28,7 @@ import org.picocontainer.lifecycle.ReflectionLifecycleStrategy; import org.picocontainer.monitors.NullComponentMonitor; import org.sonar.api.config.PropertyDefinitions; import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.StopSafeReflectionLifecycleStrategy; import static java.util.Objects.requireNonNull; @@ -49,7 +50,7 @@ public class MigrationContainerImpl extends ComponentContainer implements Migrat */ private static MutablePicoContainer createContainer(ComponentContainer parent) { ComponentMonitor componentMonitor = new NullComponentMonitor(); - ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(componentMonitor, "start", "stop", "close") { + ReflectionLifecycleStrategy lifecycleStrategy = new StopSafeReflectionLifecycleStrategy(componentMonitor) { @Override public boolean isLazy(ComponentAdapter<?> adapter) { return true; @@ -60,7 +61,7 @@ public class MigrationContainerImpl extends ComponentContainer implements Migrat @Override public void cleanup() { - stopComponents(true); + stopComponents(); } @Override diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/container/TaskContainer.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/container/TaskContainer.java index e830163292d..53d1f8d6ca1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/container/TaskContainer.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/container/TaskContainer.java @@ -25,16 +25,22 @@ import org.sonar.core.platform.ComponentContainer; import org.sonar.core.platform.ContainerPopulator; /** - * The Compute Engine container. Created for a specific parent {@link ComponentContainer} and a specific {@link CeTask}. + * The Compute Engine task container. Created for a specific parent {@link ComponentContainer} and a specific {@link CeTask}. */ -public interface TaskContainer extends ContainerPopulator.Container { +public interface TaskContainer extends ContainerPopulator.Container, AutoCloseable { ComponentContainer getParent(); /** + * Starts task container, starting any startable component in it. + */ + void bootup(); + + /** * Cleans up resources after process has been called and has returned. */ - void cleanup(); + @Override + void close(); /** * Access to the underlying pico container. diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/container/TaskContainerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/container/TaskContainerImpl.java index cd65f1c886d..853187cd562 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/container/TaskContainerImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/container/TaskContainerImpl.java @@ -32,6 +32,7 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.core.platform.ComponentContainer; import org.sonar.core.platform.ContainerPopulator; import org.sonar.core.platform.Module; +import org.sonar.core.platform.StopSafeReflectionLifecycleStrategy; import static java.util.Objects.requireNonNull; @@ -41,7 +42,6 @@ public class TaskContainerImpl extends ComponentContainer implements TaskContain super(createContainer(requireNonNull(parent)), parent.getComponentByType(PropertyDefinitions.class)); populateContainer(requireNonNull(populator)); - startComponents(); } private void populateContainer(ContainerPopulator<TaskContainer> populator) { @@ -62,7 +62,7 @@ public class TaskContainerImpl extends ComponentContainer implements TaskContain */ private static MutablePicoContainer createContainer(ComponentContainer parent) { ComponentMonitor componentMonitor = new NullComponentMonitor(); - ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(componentMonitor, "start", "stop", "close") { + ReflectionLifecycleStrategy lifecycleStrategy = new StopSafeReflectionLifecycleStrategy(componentMonitor) { @Override public boolean isLazy(ComponentAdapter<?> adapter) { return adapter.getComponentImplementation().getAnnotation(EagerStart.class) == null; @@ -73,16 +73,21 @@ public class TaskContainerImpl extends ComponentContainer implements TaskContain } @Override - public void cleanup() { - try { - stopComponents(); - } catch (Throwable t) { - Loggers.get(TaskContainerImpl.class).error("Cleanup of container failed", t); - } + public void bootup() { + startComponents(); } @Override public String toString() { return "TaskContainerImpl"; } + + @Override + public void close() { + try { + stopComponents(); + } catch (Throwable t) { + Loggers.get(TaskContainerImpl.class).error("Cleanup of container failed", t); + } + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/taskprocessor/ReportTaskProcessor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/taskprocessor/ReportTaskProcessor.java index b31c53afdb9..200b9e4c28f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/taskprocessor/ReportTaskProcessor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/taskprocessor/ReportTaskProcessor.java @@ -24,7 +24,6 @@ import java.util.Set; import javax.annotation.CheckForNull; import org.sonar.ce.queue.CeTask; import org.sonar.ce.queue.CeTaskResult; -import org.sonar.ce.settings.SettingsLoader; import org.sonar.ce.taskprocessor.CeTaskProcessor; import org.sonar.core.platform.ComponentContainer; import org.sonar.db.ce.CeTaskTypes; @@ -33,7 +32,6 @@ import org.sonar.server.computation.task.container.TaskContainer; import org.sonar.server.computation.task.projectanalysis.container.ContainerFactory; import org.sonar.server.computation.task.step.ComputationStepExecutor; import org.sonar.server.computation.taskprocessor.TaskResultHolder; -import org.sonar.server.setting.ThreadLocalSettings; public class ReportTaskProcessor implements CeTaskProcessor { @@ -69,20 +67,11 @@ public class ReportTaskProcessor implements CeTaskProcessor { @Override public CeTaskResult process(CeTask task) { - TaskContainer ceContainer = containerFactory.create(serverContainer, task, componentProviders); + try (TaskContainer ceContainer = containerFactory.create(serverContainer, task, componentProviders)) { + ceContainer.bootup(); - try { ceContainer.getComponentByType(ComputationStepExecutor.class).execute(); return ceContainer.getComponentByType(TaskResultHolder.class).getResult(); - } finally { - ensureThreadLocalIsClean(ceContainer); - - ceContainer.cleanup(); } } - - /** safety call to clear ThreadLocal even if Pico container fails to call {@link SettingsLoader#stop()}) */ - private static void ensureThreadLocalIsClean(TaskContainer ceContainer) { - ceContainer.getComponentByType(ThreadLocalSettings.class).unload(); - } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/container/TaskContainerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/container/TaskContainerImplTest.java index aa0d746b649..bd0c28462d4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/container/TaskContainerImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/container/TaskContainerImplTest.java @@ -58,16 +58,14 @@ public class TaskContainerImplTest { } @Test - public void components_are_started_lazily_unless_they_are_annotated_with_EagerStart() { + public void bootup_starts_components_lazily_unless_they_are_annotated_with_EagerStart() { final DefaultStartable defaultStartable = new DefaultStartable(); final EagerStartable eagerStartable = new EagerStartable(); - TaskContainerImpl ceContainer = new TaskContainerImpl(parent, new ContainerPopulator<TaskContainer>() { - @Override - public void populateContainer(TaskContainer container) { - container.add(defaultStartable); - container.add(eagerStartable); - } + TaskContainerImpl ceContainer = new TaskContainerImpl(parent, container -> { + container.add(defaultStartable); + container.add(eagerStartable); }); + ceContainer.bootup(); assertThat(defaultStartable.startCalls).isEqualTo(0); assertThat(defaultStartable.stopCalls).isEqualTo(0); @@ -75,6 +73,24 @@ public class TaskContainerImplTest { assertThat(eagerStartable.stopCalls).isEqualTo(0); } + @Test + public void close_stops_started_components() { + final DefaultStartable defaultStartable = new DefaultStartable(); + final EagerStartable eagerStartable = new EagerStartable(); + TaskContainerImpl ceContainer = new TaskContainerImpl(parent, container -> { + container.add(defaultStartable); + container.add(eagerStartable); + }); + ceContainer.bootup(); + + ceContainer.close(); + + assertThat(defaultStartable.startCalls).isEqualTo(0); + assertThat(defaultStartable.stopCalls).isEqualTo(0); + assertThat(eagerStartable.startCalls).isEqualTo(1); + assertThat(eagerStartable.stopCalls).isEqualTo(1); + } + public static class DefaultStartable implements Startable { protected int startCalls = 0; protected int stopCalls = 0; diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulatorTest.java index 4fbe85c0476..5231a360e3e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulatorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulatorTest.java @@ -129,12 +129,17 @@ public class ProjectAnalysisTaskContainerPopulatorTest { private List<Object> added = new ArrayList<>(); @Override + public void bootup() { + // no effect + } + + @Override public ComponentContainer getParent() { throw new UnsupportedOperationException("getParent is not implemented"); } @Override - public void cleanup() { + public void close() { throw new UnsupportedOperationException("cleanup is not implemented"); } diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java index f383b8f40fa..cd33521c5c9 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java @@ -35,14 +35,11 @@ import org.picocontainer.LifecycleStrategy; import org.picocontainer.MutablePicoContainer; import org.picocontainer.PicoContainer; import org.picocontainer.behaviors.OptInCaching; -import org.picocontainer.lifecycle.ReflectionLifecycleStrategy; import org.picocontainer.monitors.NullComponentMonitor; import org.sonar.api.batch.ScannerSide; import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.server.ServerSide; -import org.sonar.api.utils.log.Loggers; -import org.sonar.api.utils.log.Profiler; import static com.google.common.collect.ImmutableList.copyOf; import static java.util.Objects.requireNonNull; @@ -54,12 +51,8 @@ public class ComponentContainer implements ContainerPopulator.Container { public static final int COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER = 2; private static final class ExtendedDefaultPicoContainer extends DefaultPicoContainer { - private ExtendedDefaultPicoContainer(ComponentFactory componentFactory, LifecycleStrategy lifecycleStrategy, PicoContainer parent) { - super(componentFactory, lifecycleStrategy, parent); - } - - private ExtendedDefaultPicoContainer(final ComponentFactory componentFactory, final LifecycleStrategy lifecycleStrategy, final PicoContainer parent, - final ComponentMonitor componentMonitor) { + private ExtendedDefaultPicoContainer(ComponentFactory componentFactory, LifecycleStrategy lifecycleStrategy, PicoContainer parent, + ComponentMonitor componentMonitor) { super(componentFactory, lifecycleStrategy, parent, componentMonitor); } @@ -124,12 +117,10 @@ public class ComponentContainer implements ContainerPopulator.Container { } public void execute() { - boolean threw = true; try { startComponents(); - threw = false; } finally { - stopComponents(threw); + stopComponents(); } } @@ -167,21 +158,12 @@ public class ComponentContainer implements ContainerPopulator.Container { * a component twice is not authorized. */ public ComponentContainer stopComponents() { - return stopComponents(false); - } - - public ComponentContainer stopComponents(boolean swallowException) { - stopChildren(); try { + stopChildren(); if (pico.getLifecycleState().isStarted()) { pico.stop(); } pico.dispose(); - - } catch (RuntimeException e) { - if (!swallowException) { - throw PicoUtils.propagate(e); - } } finally { if (parent != null) { parent.removeChild(this); @@ -312,16 +294,8 @@ public class ComponentContainer implements ContainerPopulator.Container { } public static MutablePicoContainer createPicoContainer() { - ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(new NullComponentMonitor(), "start", "stop", "close") { - @Override - public void start(Object component) { - Profiler profiler = Profiler.createIfTrace(Loggers.get(ComponentContainer.class)); - profiler.start(); - super.start(component); - profiler.stopTrace(component.getClass().getCanonicalName() + " started"); - } - }; - return new ExtendedDefaultPicoContainer(new OptInCaching(), lifecycleStrategy, null); + NullComponentMonitor componentMonitor = new NullComponentMonitor(); + return new ExtendedDefaultPicoContainer(new OptInCaching(), new StopSafeReflectionLifecycleStrategy(componentMonitor), null, componentMonitor); } public ComponentContainer getParent() { @@ -339,4 +313,5 @@ public class ComponentContainer implements ContainerPopulator.Container { public int size() { return pico.getComponentAdapters().size(); } + } diff --git a/sonar-core/src/main/java/org/sonar/core/platform/StopSafeReflectionLifecycleStrategy.java b/sonar-core/src/main/java/org/sonar/core/platform/StopSafeReflectionLifecycleStrategy.java new file mode 100644 index 00000000000..5abf16e3e17 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/StopSafeReflectionLifecycleStrategy.java @@ -0,0 +1,60 @@ +/* + * 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.core.platform; + +import org.picocontainer.ComponentMonitor; +import org.picocontainer.lifecycle.ReflectionLifecycleStrategy; +import org.sonar.api.utils.log.Loggers; + +/** + * A {@link ReflectionLifecycleStrategy} which: + * <li> + * <ul>implements support for methods {@code start()}, {@code stop()} and {@code close()} as methods of startable, + * stoppable and/or disposable components in a SonarQube container (whichever side the container is on)</ul> + * <ul>ensures that all stoppable and disposable components in a given container are stopped and/or disposed of + * even if a {@link RuntimeException} or a {@link Error} is thrown by one or more of those stoppable and/or + * disposable components</ul> + * </li> + */ +public class StopSafeReflectionLifecycleStrategy extends ReflectionLifecycleStrategy { + public StopSafeReflectionLifecycleStrategy(ComponentMonitor componentMonitor) { + super(componentMonitor, "start", "stop", "close"); + } + + @Override + public void stop(Object component) { + try { + super.stop(component); + } catch (RuntimeException | Error e) { + Loggers.get(StopSafeReflectionLifecycleStrategy.class) + .warn("Stopping of component {} failed", component.getClass().getCanonicalName(), e); + } + } + + @Override + public void dispose(Object component) { + try { + super.dispose(component); + } catch (RuntimeException | Error e) { + Loggers.get(StopSafeReflectionLifecycleStrategy.class) + .warn("Dispose of component {} failed", component.getClass().getCanonicalName(), e); + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java index a877d1787ac..9232399b25f 100644 --- a/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java @@ -19,7 +19,9 @@ */ package org.sonar.core.platform; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -48,22 +50,22 @@ public class ComponentContainerTest { @Test public void should_start_and_stop() { ComponentContainer container = spy(new ComponentContainer()); - container.addSingleton(StartableComponent.class); + container.addSingleton(StartableStoppableComponent.class); container.startComponents(); - assertThat(container.getComponentByType(StartableComponent.class).started).isTrue(); - assertThat(container.getComponentByType(StartableComponent.class).stopped).isFalse(); + assertThat(container.getComponentByType(StartableStoppableComponent.class).started).isTrue(); + assertThat(container.getComponentByType(StartableStoppableComponent.class).stopped).isFalse(); verify(container).doBeforeStart(); verify(container).doAfterStart(); container.stopComponents(); - assertThat(container.getComponentByType(StartableComponent.class).stopped).isTrue(); + assertThat(container.getComponentByType(StartableStoppableComponent.class).stopped).isTrue(); } @Test public void should_start_and_stop_hierarchy_of_containers() { - StartableComponent parentComponent = new StartableComponent(); - final StartableComponent childComponent = new StartableComponent(); + StartableStoppableComponent parentComponent = new StartableStoppableComponent(); + final StartableStoppableComponent childComponent = new StartableStoppableComponent(); ComponentContainer parentContainer = new ComponentContainer() { @Override public void doAfterStart() { @@ -82,8 +84,8 @@ public class ComponentContainerTest { @Test public void should_stop_hierarchy_of_containers_on_failure() { - StartableComponent parentComponent = new StartableComponent(); - final StartableComponent childComponent1 = new StartableComponent(); + StartableStoppableComponent parentComponent = new StartableStoppableComponent(); + final StartableStoppableComponent childComponent1 = new StartableStoppableComponent(); final UnstartableComponent childComponent2 = new UnstartableComponent(); ComponentContainer parentContainer = new ComponentContainer() { @Override @@ -112,15 +114,15 @@ public class ComponentContainerTest { parent.startComponents(); ComponentContainer child = parent.createChild(); - child.addSingleton(StartableComponent.class); + child.addSingleton(StartableStoppableComponent.class); child.startComponents(); assertThat(child.getParent()).isSameAs(parent); assertThat(parent.getChildren()).containsOnly(child); assertThat(child.getComponentByType(ComponentContainer.class)).isSameAs(child); assertThat(parent.getComponentByType(ComponentContainer.class)).isSameAs(parent); - assertThat(child.getComponentByType(StartableComponent.class)).isNotNull(); - assertThat(parent.getComponentByType(StartableComponent.class)).isNull(); + assertThat(child.getComponentByType(StartableStoppableComponent.class)).isNotNull(); + assertThat(parent.getComponentByType(StartableStoppableComponent.class)).isNull(); parent.stopComponents(); } @@ -154,17 +156,16 @@ public class ComponentContainerTest { assertThat(parent.getChildren()).isEmpty(); } - @Test public void shouldForwardStartAndStopToDescendants() { ComponentContainer grandParent = new ComponentContainer(); ComponentContainer parent = grandParent.createChild(); ComponentContainer child = parent.createChild(); - child.addSingleton(StartableComponent.class); + child.addSingleton(StartableStoppableComponent.class); grandParent.startComponents(); - StartableComponent component = child.getComponentByType(StartableComponent.class); + StartableStoppableComponent component = child.getComponentByType(StartableStoppableComponent.class); assertTrue(component.started); parent.stopComponents(); @@ -254,7 +255,7 @@ public class ComponentContainerTest { @Test public void test_start_failure() { ComponentContainer container = new ComponentContainer(); - StartableComponent startable = new StartableComponent(); + StartableStoppableComponent startable = new StartableStoppableComponent(); container.add(startable, UnstartableComponent.class); try { @@ -269,26 +270,38 @@ public class ComponentContainerTest { } @Test - public void test_stop_failure() { + public void stop_container_does_not_fail_and_all_stoppable_components_are_stopped_even_if_one_or_more_stop_method_call_fail() { ComponentContainer container = new ComponentContainer(); - StartableComponent startable = new StartableComponent(); - container.add(startable, UnstoppableComponent.class); + container.add(FailingStopWithISEComponent.class, FailingStopWithISEComponent2.class, FailingStopWithErrorComponent.class, FailingStopWithErrorComponent2.class); + container.startComponents(); + StartableStoppableComponent[] components = { + container.getComponentByType(FailingStopWithISEComponent.class), + container.getComponentByType(FailingStopWithISEComponent2.class), + container.getComponentByType(FailingStopWithErrorComponent.class), + container.getComponentByType(FailingStopWithErrorComponent2.class) + }; - try { - container.execute(); - fail(); - } catch (Exception e) { - assertThat(startable.started).isTrue(); + container.stopComponents(); + + Arrays.stream(components).forEach(startableComponent -> assertThat(startableComponent.stopped).isTrue()); + } + + @Test + public void stop_container_stops_all_stoppable_components_even_in_case_of_OOM_in_any_stop_method() { + ComponentContainer container = new ComponentContainer(); + container.add(FailingStopWithOOMComponent.class, FailingStopWithOOMComponent2.class); + container.startComponents(); + StartableStoppableComponent[] components = { + container.getComponentByType(FailingStopWithOOMComponent.class), + container.getComponentByType(FailingStopWithOOMComponent2.class) + }; - // container should stop the components that have already been started - // ... but that's not the case - } } @Test public void stop_exception_should_not_hide_start_exception() { ComponentContainer container = new ComponentContainer(); - container.add(UnstartableComponent.class, UnstoppableComponent.class); + container.add(UnstartableComponent.class, FailingStopWithISEComponent.class); thrown.expect(IllegalStateException.class); thrown.expectMessage("Fail to start"); @@ -298,7 +311,7 @@ public class ComponentContainerTest { @Test public void should_execute_components() { ComponentContainer container = new ComponentContainer(); - StartableComponent component = new StartableComponent(); + StartableStoppableComponent component = new StartableStoppableComponent(); container.add(component); container.execute(); @@ -338,7 +351,7 @@ public class ComponentContainerTest { assertThat(component.isClosedAfterStop).isTrue(); } - public static class StartableComponent { + public static class StartableStoppableComponent { public boolean started = false; public boolean stopped = false; @@ -361,15 +374,44 @@ public class ComponentContainerTest { } } - public static class UnstoppableComponent { - public void start() { + public static class FailingStopWithISEComponent extends StartableStoppableComponent { + public void stop() { + super.stop(); + throw new IllegalStateException("Faking IllegalStateException thrown by stop method of " + getClass().getSimpleName()); } + } + + public static class FailingStopWithISEComponent2 extends FailingStopWithErrorComponent { + } + public static class FailingStopWithErrorComponent extends StartableStoppableComponent { public void stop() { - throw new IllegalStateException("Fail to stop"); + super.stop(); + throw new Error("Faking Error thrown by stop method of " + getClass().getSimpleName()); } } + public static class FailingStopWithErrorComponent2 extends FailingStopWithErrorComponent { + } + + public static class FailingStopWithOOMComponent extends StartableStoppableComponent { + public void stop() { + super.stop(); + consumeAvailableMemory(); + } + + private static List<Object> consumeAvailableMemory() { + List<Object> holder = new ArrayList<>(); + while (true) { + holder.add(new byte[128 * 1024]); + } + } + } + + public static class FailingStopWithOOMComponent2 extends FailingStopWithOOMComponent { + + } + @Property(key = "foo", defaultValue = "bar", name = "Foo") public static class ComponentWithProperty { diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/Batch.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/Batch.java index 9d745c53008..d324618ea43 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/Batch.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/Batch.java @@ -67,12 +67,10 @@ public final class Batch { public synchronized Batch execute() { configureLogging(); doStart(); - boolean threw = true; try { doExecuteTask(globalProperties); - threw = false; } finally { - doStop(threw); + doStop(); } return this; } @@ -150,12 +148,12 @@ public final class Batch { public synchronized void stop() { checkStarted(); configureLogging(); - doStop(false); + doStop(); } - private void doStop(boolean swallowException) { + private void doStop() { try { - bootstrapContainer.stopComponents(swallowException); + bootstrapContainer.stopComponents(); } catch (RuntimeException e) { throw handleException(e); } diff --git a/tests/plugins/fake-governance-plugin/src/main/java/FakeGovernancePlugin.java b/tests/plugins/fake-governance-plugin/src/main/java/FakeGovernancePlugin.java index 19f6fdf68c3..b37e201d61f 100644 --- a/tests/plugins/fake-governance-plugin/src/main/java/FakeGovernancePlugin.java +++ b/tests/plugins/fake-governance-plugin/src/main/java/FakeGovernancePlugin.java @@ -18,6 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import ce.BombConfig; +import ce.ComponentBombReportAnalysisComponentProvider; +import ce.IseTaskProcessor; +import ce.OkTaskProcessor; +import ce.OomTaskProcessor; +import ce.ws.BombActivatorAction; +import ce.ws.FakeGovWs; +import ce.ws.SubmitAction; import org.sonar.api.Plugin; import systemPasscode.SystemPasscodeWebService; import workerCount.FakeWorkerCountProviderImpl; @@ -36,6 +44,20 @@ public class FakeGovernancePlugin implements Plugin { context.addExtension(LatchControllerWorkerMeasureComputer.class); context.addExtension(RefreshWorkerCountAction.class); context.addExtension(SystemPasscodeWebService.class); + + // WS api/fake_gov + context.addExtension(FakeGovWs.class); + + // failing CE tasks + context.addExtension(SubmitAction.class); + context.addExtension(OomTaskProcessor.class); + context.addExtension(IseTaskProcessor.class); + context.addExtension(OkTaskProcessor.class); + + // component bombs injection into the Report Analysis processing container in the CE + context.addExtension(BombConfig.class); + context.addExtension(ComponentBombReportAnalysisComponentProvider.class); + context.addExtension(BombActivatorAction.class); } } diff --git a/tests/plugins/fake-governance-plugin/src/main/java/ce/BombConfig.java b/tests/plugins/fake-governance-plugin/src/main/java/ce/BombConfig.java new file mode 100644 index 00000000000..3e5af086b86 --- /dev/null +++ b/tests/plugins/fake-governance-plugin/src/main/java/ce/BombConfig.java @@ -0,0 +1,102 @@ +/* + * 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 ce; + +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.server.ServerSide; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; + +@ServerSide +@ComputeEngineSide +public class BombConfig { + private static final String OOM_START_BOMB_KEY = "oomStartBomb"; + private static final String ISE_START_BOMB_KEY = "iseStartBomb"; + private static final String OOM_STOP_BOMB_KEY = "oomStopBomb"; + private static final String ISE_STOP_BOMB_KEY = "iseStopBomb"; + + private final DbClient dbClient; + + public BombConfig(DbClient dbClient) { + this.dbClient = dbClient; + } + + public void reset() { + try (DbSession dbSession = dbClient.openSession(false)) { + dbClient.internalPropertiesDao().save(dbSession, OOM_START_BOMB_KEY, String.valueOf(false)); + dbClient.internalPropertiesDao().save(dbSession, ISE_START_BOMB_KEY, String.valueOf(false)); + dbClient.internalPropertiesDao().save(dbSession, OOM_STOP_BOMB_KEY, String.valueOf(false)); + dbClient.internalPropertiesDao().save(dbSession, ISE_STOP_BOMB_KEY, String.valueOf(false)); + dbSession.commit(); + } + } + + public boolean isOomStartBomb() { + try (DbSession dbSession = dbClient.openSession(false)) { + return dbClient.internalPropertiesDao().selectByKey(dbSession, OOM_START_BOMB_KEY).map(Boolean::valueOf).orElse(false); + } + } + + public void setOomStartBomb(boolean oomStartBomb) { + try (DbSession dbSession = dbClient.openSession(false)) { + dbClient.internalPropertiesDao().save(dbSession, OOM_START_BOMB_KEY, String.valueOf(oomStartBomb)); + dbSession.commit(); + } + } + + public boolean isIseStartBomb() { + try (DbSession dbSession = dbClient.openSession(false)) { + return dbClient.internalPropertiesDao().selectByKey(dbSession, ISE_START_BOMB_KEY).map(Boolean::valueOf).orElse(false); + } + } + + public void setIseStartBomb(boolean iseStartBomb) { + try (DbSession dbSession = dbClient.openSession(false)) { + dbClient.internalPropertiesDao().save(dbSession, ISE_START_BOMB_KEY, String.valueOf(iseStartBomb)); + dbSession.commit(); + } + } + + public boolean isOomStopBomb() { + try (DbSession dbSession = dbClient.openSession(false)) { + return dbClient.internalPropertiesDao().selectByKey(dbSession, OOM_STOP_BOMB_KEY).map(Boolean::valueOf).orElse(false); + } + } + + public void setOomStopBomb(boolean oomStopBomb) { + try (DbSession dbSession = dbClient.openSession(false)) { + dbClient.internalPropertiesDao().save(dbSession, OOM_STOP_BOMB_KEY, String.valueOf(oomStopBomb)); + dbSession.commit(); + } + } + + public boolean isIseStopBomb() { + try (DbSession dbSession = dbClient.openSession(false)) { + return dbClient.internalPropertiesDao().selectByKey(dbSession, ISE_STOP_BOMB_KEY).map(Boolean::valueOf).orElse(false); + } + } + + public void setIseStopBomb(boolean iseStopBomb) { + try (DbSession dbSession = dbClient.openSession(false)) { + dbClient.internalPropertiesDao().save(dbSession, ISE_STOP_BOMB_KEY, String.valueOf(iseStopBomb)); + dbSession.commit(); + } + } +} diff --git a/tests/plugins/fake-governance-plugin/src/main/java/ce/ComponentBombReportAnalysisComponentProvider.java b/tests/plugins/fake-governance-plugin/src/main/java/ce/ComponentBombReportAnalysisComponentProvider.java new file mode 100644 index 00000000000..166f526fca4 --- /dev/null +++ b/tests/plugins/fake-governance-plugin/src/main/java/ce/ComponentBombReportAnalysisComponentProvider.java @@ -0,0 +1,109 @@ +/* + * 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 ce; + +import java.util.List; +import org.picocontainer.Startable; +import org.sonar.plugin.ce.ReportAnalysisComponentProvider; +import org.sonar.server.computation.task.container.EagerStart; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +public class ComponentBombReportAnalysisComponentProvider implements ReportAnalysisComponentProvider { + private final BombConfig bombConfig; + + public ComponentBombReportAnalysisComponentProvider(BombConfig bombConfig) { + this.bombConfig = bombConfig; + } + + @Override + public List<Object> getComponents() { + if (bombConfig.isOomStartBomb()) { + return singletonList(OOMFailingStartComponent.class); + } + if (bombConfig.isIseStartBomb()) { + return singletonList(ISEFailingStartComponent.class); + } + if (bombConfig.isOomStopBomb()) { + return singletonList(OOMFailingStopComponent.class); + } + if (bombConfig.isIseStopBomb()) { + return singletonList(ISEFailingStopComponent.class); + } + return emptyList(); + } + + @EagerStart + public static final class OOMFailingStartComponent implements Startable { + + @Override + public void start() { + OOMGenerator.consumeAvailableMemory(); + } + + @Override + public void stop() { + // nothing to do + } + } + + @EagerStart + public static final class ISEFailingStartComponent implements Startable { + + @Override + public void start() { + throw new IllegalStateException("Faking an IllegalStateException thrown by a startable component in the Analysis Report processing container"); + } + + @Override + public void stop() { + // nothing to do + } + } + + @EagerStart + public static final class OOMFailingStopComponent implements Startable { + + @Override + public void start() { + // nothing to do + } + + @Override + public void stop() { + OOMGenerator.consumeAvailableMemory(); + } + } + + @EagerStart + public static final class ISEFailingStopComponent implements Startable { + + @Override + public void start() { + // nothing to do + } + + @Override + public void stop() { + throw new IllegalStateException("Faking an IllegalStateException thrown by a stoppable component in the Analysis Report processing container"); + } + } +} diff --git a/tests/plugins/fake-governance-plugin/src/main/java/ce/IseTaskProcessor.java b/tests/plugins/fake-governance-plugin/src/main/java/ce/IseTaskProcessor.java new file mode 100644 index 00000000000..a6960d1b049 --- /dev/null +++ b/tests/plugins/fake-governance-plugin/src/main/java/ce/IseTaskProcessor.java @@ -0,0 +1,38 @@ +/* + * 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 ce; + +import java.util.Collections; +import java.util.Set; +import org.sonar.ce.queue.CeTask; +import org.sonar.ce.queue.CeTaskResult; +import org.sonar.ce.taskprocessor.CeTaskProcessor; + +public class IseTaskProcessor implements CeTaskProcessor { + @Override + public Set<String> getHandledCeTaskTypes() { + return Collections.singleton("ISE"); + } + + @Override + public CeTaskResult process(CeTask task) { + throw new IllegalStateException("Faking an IllegalStateException thrown processing a task"); + } +} diff --git a/tests/plugins/fake-governance-plugin/src/main/java/ce/OOMGenerator.java b/tests/plugins/fake-governance-plugin/src/main/java/ce/OOMGenerator.java new file mode 100644 index 00000000000..4fcef07cb80 --- /dev/null +++ b/tests/plugins/fake-governance-plugin/src/main/java/ce/OOMGenerator.java @@ -0,0 +1,36 @@ +/* + * 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 ce; + +import java.util.ArrayList; +import java.util.List; + +public final class OOMGenerator { + private OOMGenerator() { + // prevents instantiation + } + + public static List<Object> consumeAvailableMemory() { + List<Object> holder = new ArrayList<>(); + while (true) { + holder.add(new byte[128 * 1024]); + } + } +} diff --git a/tests/plugins/fake-governance-plugin/src/main/java/ce/OkTaskProcessor.java b/tests/plugins/fake-governance-plugin/src/main/java/ce/OkTaskProcessor.java new file mode 100644 index 00000000000..92e05032fa0 --- /dev/null +++ b/tests/plugins/fake-governance-plugin/src/main/java/ce/OkTaskProcessor.java @@ -0,0 +1,41 @@ +/* + * 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 ce; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import org.sonar.ce.queue.CeTask; +import org.sonar.ce.queue.CeTaskResult; +import org.sonar.ce.taskprocessor.CeTaskProcessor; + +public class OkTaskProcessor implements CeTaskProcessor { + @Override + public Set<String> getHandledCeTaskTypes() { + return Collections.singleton("OK"); + } + + @Override + public CeTaskResult process(CeTask task) { + return Optional::empty; + } + + +} diff --git a/tests/plugins/fake-governance-plugin/src/main/java/ce/OomTaskProcessor.java b/tests/plugins/fake-governance-plugin/src/main/java/ce/OomTaskProcessor.java new file mode 100644 index 00000000000..384d3201daa --- /dev/null +++ b/tests/plugins/fake-governance-plugin/src/main/java/ce/OomTaskProcessor.java @@ -0,0 +1,39 @@ +/* + * 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 ce; + +import java.util.Collections; +import java.util.Set; +import org.sonar.ce.queue.CeTask; +import org.sonar.ce.queue.CeTaskResult; +import org.sonar.ce.taskprocessor.CeTaskProcessor; + +public class OomTaskProcessor implements CeTaskProcessor { + @Override + public Set<String> getHandledCeTaskTypes() { + return Collections.singleton("OOM"); + } + + @Override + public CeTaskResult process(CeTask task) { + OOMGenerator.consumeAvailableMemory(); + return null; + } +} diff --git a/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/BombActivatorAction.java b/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/BombActivatorAction.java new file mode 100644 index 00000000000..e2366b8e16a --- /dev/null +++ b/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/BombActivatorAction.java @@ -0,0 +1,82 @@ +/* + * 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 ce.ws; + +import ce.BombConfig; +import java.util.Arrays; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; + +import static java.util.stream.Collectors.toList; + +public class BombActivatorAction implements FakeGoVWsAction { + + private static final String PARAM_BOMB_TYPE = "type"; + + private final BombConfig bombConfig; + + public BombActivatorAction(BombConfig bombConfig) { + this.bombConfig = bombConfig; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction("activate_bomb") + .setPost(true) + .setHandler(this); + action.createParam(PARAM_BOMB_TYPE) + .setRequired(true) + .setPossibleValues(Arrays.stream(BombType.values()).map(Enum::toString).collect(toList())); + } + + @Override + public void handle(Request request, Response response) throws Exception { + BombType bombType = BombType.valueOf(request.mandatoryParam(PARAM_BOMB_TYPE)); + + bombConfig.reset(); + switch (bombType) { + case ISE_START: + bombConfig.setIseStartBomb(true); + break; + case OOM_START: + bombConfig.setOomStartBomb(true); + break; + case ISE_STOP: + bombConfig.setIseStopBomb(true); + break; + case OOM_STOP: + bombConfig.setOomStopBomb(true); + break; + case NONE: + break; + default: + throw new IllegalArgumentException("Unsupported bomb type " + bombType); + } + + response.noContent(); + } + + enum BombType { + NONE, OOM_START, ISE_START, OOM_STOP, ISE_STOP + + } + +} diff --git a/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/FakeGoVWsAction.java b/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/FakeGoVWsAction.java new file mode 100644 index 00000000000..bd361f92961 --- /dev/null +++ b/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/FakeGoVWsAction.java @@ -0,0 +1,25 @@ +/* + * 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 ce.ws; + +import org.sonar.server.ws.WsAction; + +public interface FakeGoVWsAction extends WsAction { +} diff --git a/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/FakeGovWs.java b/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/FakeGovWs.java new file mode 100644 index 00000000000..9d89cfbb795 --- /dev/null +++ b/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/FakeGovWs.java @@ -0,0 +1,38 @@ +/* + * 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 ce.ws; + +import java.util.Arrays; +import org.sonar.api.server.ws.WebService; + +public class FakeGovWs implements WebService { + private final FakeGoVWsAction[] actions; + + public FakeGovWs(FakeGoVWsAction[] actions) { + this.actions = actions; + } + + @Override + public void define(Context context) { + NewController controller = context.createController("api/fake_gov"); + Arrays.stream(actions).forEach(action -> action.define(controller)); + controller.done(); + } +} diff --git a/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/SubmitAction.java b/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/SubmitAction.java new file mode 100644 index 00000000000..42f037f819c --- /dev/null +++ b/tests/plugins/fake-governance-plugin/src/main/java/ce/ws/SubmitAction.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 ce.ws; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.ce.queue.CeQueue; +import org.sonar.ce.queue.CeTaskSubmit; + +public class SubmitAction implements FakeGoVWsAction { + + private static final String PARAM_TYPE = "type"; + + private final CeQueue ceQueue; + + public SubmitAction(CeQueue ceQueue) { + this.ceQueue = ceQueue; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction("submit") + .setPost(true) + .setHandler(this); + action.createParam(PARAM_TYPE) + .setRequired(true) + .setPossibleValues("OOM", "OK", "ISE"); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String type = request.mandatoryParam(PARAM_TYPE); + + CeTaskSubmit.Builder submit = ceQueue.prepareSubmit(); + submit.setType(type); + + ceQueue.submit(submit.build()); + response.noContent(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/ce/CeWorkersTest.java b/tests/src/test/java/org/sonarqube/tests/ce/CeWorkersTest.java index 491c671a3ce..64d0625e192 100644 --- a/tests/src/test/java/org/sonarqube/tests/ce/CeWorkersTest.java +++ b/tests/src/test/java/org/sonarqube/tests/ce/CeWorkersTest.java @@ -38,12 +38,15 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; import javax.annotation.Nullable; +import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonarqube.ws.WsCe; +import org.sonarqube.ws.client.PostRequest; import org.sonarqube.ws.client.WsClient; import org.sonarqube.ws.client.ce.ActivityWsRequest; import util.ItUtils; @@ -81,6 +84,8 @@ public class CeWorkersTest { OrchestratorBuilder builder = Orchestrator.builderEnv() .addPlugin(pluginArtifact("fake-governance-plugin")) .setServerProperty("fakeGoverance.workerLatch.sharedMemoryFile", sharedMemory.getAbsolutePath()) + // overwrite default value to display heap dump on OOM and reduce max heap + .setServerProperty("sonar.ce.javaOpts", "-Xmx256m -Xms128m") .addPlugin(xooPlugin()); orchestrator = builder.build(); orchestrator.start(); @@ -96,6 +101,145 @@ public class CeWorkersTest { } } + @Before + public void setup() throws Exception { + unlockWorkersAndResetWorkerCount(); + } + + @After + public void tearDown() throws Exception { + unlockWorkersAndResetWorkerCount(); + } + + private void unlockWorkersAndResetWorkerCount() throws IOException { + RandomAccessFile randomAccessFile = null; + try { + randomAccessFile = new RandomAccessFile(sharedMemory, "rw"); + MappedByteBuffer mappedByteBuffer = initMappedByteBuffer(randomAccessFile); + releaseAnyAnalysisWithFakeGovernancePlugin(mappedByteBuffer); + updateWorkerCount(1); + } finally { + close(randomAccessFile); + } + } + + @Test + public void ce_worker_is_resilient_to_OOM_and_ISE_during_processing_of_a_task() throws InterruptedException { + submitFakeTask("OOM"); + + waitForEmptyQueue(); + + assertThat(adminWsClient.ce().activity(new ActivityWsRequest() + .setType("OOM") + .setStatus(ImmutableList.of("FAILED"))) + .getTasksCount()) + .isEqualTo(1); + + submitFakeTask("OK"); + + waitForEmptyQueue(); + + assertThat(adminWsClient.ce().activity(new ActivityWsRequest() + .setType("OK") + .setStatus(ImmutableList.of("SUCCESS"))) + .getTasksCount()) + .isEqualTo(1); + + submitFakeTask("ISE"); + + waitForEmptyQueue(); + + assertThat(adminWsClient.ce().activity(new ActivityWsRequest() + .setType("ISE") + .setStatus(ImmutableList.of("FAILED"))) + .getTasksCount()) + .isEqualTo(1); + + submitFakeTask("OK"); + + waitForEmptyQueue(); + + assertThat(adminWsClient.ce().activity(new ActivityWsRequest() + .setType("OK") + .setStatus(ImmutableList.of("SUCCESS"))) + .getTasksCount()) + .isEqualTo(2); + } + + private void submitFakeTask(String type) { + adminWsClient.wsConnector().call(new PostRequest("api/fake_gov/submit") + .setParam("type", type)) + .failIfNotSuccessful(); + } + + @Test + public void ce_worker_is_resilient_to_OOM_and_RuntimeException_when_starting_or_stopping_analysis_report_container() throws IOException { + int initSuccessReportTaskCount = adminWsClient.ce().activity(new ActivityWsRequest() + .setType("REPORT") + .setStatus(ImmutableList.of("SUCCESS"))) + .getTasksCount(); + int initFailedReportTaskCount = adminWsClient.ce().activity(new ActivityWsRequest() + .setType("REPORT") + .setStatus(ImmutableList.of("FAILED"))) + .getTasksCount(); + + SonarScanner sonarRunner = SonarScanner.create(ItUtils.projectDir("shared/xoo-sample")); + orchestrator.executeBuild(sonarRunner, true); + + enableComponentBomb("OOM_STOP"); + + orchestrator.executeBuild(sonarRunner, true); + + enableComponentBomb("NONE"); + + orchestrator.executeBuild(sonarRunner, true); + + enableComponentBomb("ISE_START"); + + orchestrator.executeBuild(sonarRunner, true); + + enableComponentBomb("NONE"); + + orchestrator.executeBuild(sonarRunner, true); + + enableComponentBomb("ISE_STOP"); + + orchestrator.executeBuild(sonarRunner, true); + + enableComponentBomb("NONE"); + + orchestrator.executeBuild(sonarRunner, true); + + enableComponentBomb("OOM_START"); + + orchestrator.executeBuild(sonarRunner, true); + + enableComponentBomb("NONE"); + + orchestrator.executeBuild(sonarRunner, true); + + // failure while starting components does fail the tasks + assertThat(adminWsClient.ce().activity(new ActivityWsRequest() + .setType("REPORT") + .setStatus(ImmutableList.of("FAILED"))) + .getTasksCount()) + .isEqualTo(initFailedReportTaskCount + 2); + + // failure while stopping components does not fail the tasks + assertThat(adminWsClient.ce().activity(new ActivityWsRequest() + .setType("REPORT") + .setStatus(ImmutableList.of("SUCCESS"))) + .getTasksCount()) + .isEqualTo(initSuccessReportTaskCount + 7); + + } + + private void enableComponentBomb(String type) { + adminWsClient.wsConnector().call(new PostRequest("api/fake_gov/activate_bomb") + .setParam("type", type)) + .failIfNotSuccessful(); + } + @Test public void enabled_worker_count_is_initially_1_and_can_be_changed_dynamically_by_plugin() throws IOException { assertThat(Files.lines(orchestrator.getServer().getCeLogs().toPath()) @@ -109,7 +253,7 @@ public class CeWorkersTest { verifyAnalysesRunInParallel(mappedByteBuffer, 1); - /* 2 <= newWorkerCount <= 7 */ + /* 4 <= newWorkerCount <= 7 */ int newWorkerCount = 4 + new Random().nextInt(4); updateWorkerCount(newWorkerCount); @@ -234,4 +378,19 @@ public class CeWorkersTest { e.printStackTrace(); } } + + private void waitForEmptyQueue() throws InterruptedException { + int delay = 200; + int timeout = 5 * 10; // 10 seconds + int i = 0; + int tasksCount; + do { + Thread.sleep(delay); + tasksCount = adminWsClient.ce().activity(new ActivityWsRequest() + .setStatus(ImmutableList.of("PENDING", "IN_PROGRESS"))) + .getTasksCount(); + i++; + } while (i <= timeout && tasksCount > 0); + assertThat(tasksCount).describedAs("Failed to get to an empty CE queue in a timely fashion").isZero(); + } } |