]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10028 move CeQueueCleaner to Web to support clustering
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 25 Jul 2019 08:59:37 +0000 (10:59 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 30 Jul 2019 18:24:27 +0000 (20:24 +0200)
15 files changed:
server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeQueue.java
server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeQueueImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/queue/CeQueueCleaner.java [deleted file]
server/sonar-ce/src/main/java/org/sonar/ce/queue/InternalCeQueue.java
server/sonar-ce/src/main/java/org/sonar/ce/queue/InternalCeQueueImpl.java
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/queue/CeQueueCleanerTest.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/ce/queue/CeQueueCleaner.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
server/sonar-server/src/test/java/org/sonar/server/ce/queue/CeQueueCleanerTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java [new file with mode: 0644]

index bb1b7281c02cfd62ba9efab306c513df4db7d798..c2bf3568dd6ec211f01b0bfb2e3763a920867e7b 100644 (file)
@@ -25,6 +25,7 @@ import java.util.Optional;
 import javax.annotation.Nullable;
 import org.sonar.ce.task.CeTask;
 import org.sonar.db.DbSession;
+import org.sonar.db.ce.CeActivityDto;
 import org.sonar.db.ce.CeQueueDto;
 
 /**
@@ -112,6 +113,16 @@ public interface CeQueue {
 
   WorkersPauseStatus getWorkersPauseStatus();
 
+  /**
+   * Removes all the tasks from the queue, whatever their status. They are marked
+   * as {@link CeActivityDto.Status#CANCELED} in past activity.
+   * This method can NOT be called when  workers are being executed, as in progress
+   * tasks can't be killed.
+   *
+   * @return the number of canceled tasks
+   */
+  int clear();
+
   enum SubmitOption {
     UNIQUE_QUEUE_PER_MAIN_COMPONENT
   }
index 39f832f8f30c97ea335c5ac27adcd586bc509497..b9862d298b4aad87354e9968caebc19c65f65e0b 100644 (file)
@@ -340,6 +340,11 @@ public class CeQueueImpl implements CeQueue {
     }
   }
 
+  @Override
+  public int clear() {
+    return cancelAll(true);
+  }
+
   CeTask convertToTask(DbSession dbSession, CeQueueDto taskDto, Map<String, String> characteristics, @Nullable ComponentDto component, @Nullable ComponentDto mainComponent) {
     CeTask.Builder builder = new CeTask.Builder()
       .setUuid(taskDto.getUuid())
index 2358876fb0e92ef4999ae497f6e59f4f7fddc162..19ffc9671e82c3beac46d1b79e15d9d41162cae3 100644 (file)
@@ -59,7 +59,6 @@ import org.sonar.ce.monitoring.DistributedCEQueueStatusImpl;
 import org.sonar.ce.platform.CECoreExtensionsInstaller;
 import org.sonar.ce.platform.ComputeEngineExtensionInstaller;
 import org.sonar.ce.platform.DatabaseCompatibility;
-import org.sonar.ce.queue.CeQueueCleaner;
 import org.sonar.ce.queue.PurgeCeActivities;
 import org.sonar.ce.task.projectanalysis.ProjectAnalysisTaskModule;
 import org.sonar.ce.task.projectanalysis.analysis.ProjectConfigurationFactory;
@@ -117,7 +116,6 @@ import org.sonar.server.notification.email.EmailNotificationChannel;
 import org.sonar.server.organization.BillingValidationsProxyImpl;
 import org.sonar.server.organization.DefaultOrganizationProviderImpl;
 import org.sonar.server.organization.OrganizationFlagsImpl;
-import org.sonar.server.platform.DefaultServerUpgradeStatus;
 import org.sonar.server.platform.OfficialDistribution;
 import org.sonar.server.platform.ServerFileSystemImpl;
 import org.sonar.server.platform.ServerImpl;
@@ -330,7 +328,6 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
 
       // add ReadOnlyPropertiesDao at level2 again so that it shadows PropertiesDao
       ReadOnlyPropertiesDao.class,
-      DefaultServerUpgradeStatus.class,
 
       // plugins
       PluginClassloaderFactory.class,
@@ -485,8 +482,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
   private static Object[] startupComponents() {
     return new Object[] {
       ServerLifecycleNotifier.class,
-      PurgeCeActivities.class,
-      CeQueueCleaner.class
+      PurgeCeActivities.class
     };
   }
 
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/queue/CeQueueCleaner.java b/server/sonar-ce/src/main/java/org/sonar/ce/queue/CeQueueCleaner.java
deleted file mode 100644 (file)
index 22e2c6b..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.ce.queue;
-
-import java.util.List;
-import org.picocontainer.Startable;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.process.ProcessProperties;
-
-/**
- * Cleans-up the Compute Engine queue.
- * CE workers must not be started before execution of this class.
- */
-@ComputeEngineSide
-public class CeQueueCleaner implements Startable {
-
-  private static final Logger LOGGER = Loggers.get(CeQueueCleaner.class);
-
-  private final DbClient dbClient;
-  private final ServerUpgradeStatus serverUpgradeStatus;
-  private final InternalCeQueue queue;
-  private final Configuration configuration;
-
-  public CeQueueCleaner(DbClient dbClient, ServerUpgradeStatus serverUpgradeStatus, InternalCeQueue queue, Configuration configuration) {
-    this.dbClient = dbClient;
-    this.serverUpgradeStatus = serverUpgradeStatus;
-    this.queue = queue;
-    this.configuration = configuration;
-  }
-
-  @Override
-  public void start() {
-    if (serverUpgradeStatus.isUpgraded() && !isBlueGreenDeployment()) {
-      cleanOnUpgrade();
-    }
-    cleanUpTaskInputOrphans();
-  }
-
-  private boolean isBlueGreenDeployment() {
-    return configuration.getBoolean(ProcessProperties.Property.BLUE_GREEN_ENABLED.getKey()).orElse(false);
-  }
-
-  private void cleanOnUpgrade() {
-    // we assume that pending tasks are not compatible with the new version
-    // and can't be processed
-    LOGGER.info("Cancel all pending tasks (due to upgrade)");
-    queue.clear();
-  }
-
-  private void cleanUpTaskInputOrphans() {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      // Reports that have been processed are not kept in database yet.
-      // They are supposed to be systematically dropped.
-      // Let's clean-up orphans if any.
-      List<String> uuids = dbClient.ceTaskInputDao().selectUuidsNotInQueue(dbSession);
-      dbClient.ceTaskInputDao().deleteByUuids(dbSession, uuids);
-      dbSession.commit();
-    }
-  }
-
-  @Override
-  public void stop() {
-    // nothing to do
-  }
-}
index c8a06e367b2838707b74f0bf86d04517e994461f..44c8561553dc0090f38a99159639dce36f88f58f 100644 (file)
@@ -49,16 +49,6 @@ public interface InternalCeQueue extends CeQueue {
    */
   Optional<CeTask> peek(String workerUuid);
 
-  /**
-   * Removes all the tasks from the queue, whatever their status. They are marked
-   * as {@link Status#CANCELED} in past activity.
-   * This method can NOT be called when  workers are being executed, as in progress
-   * tasks can't be killed.
-   *
-   * @return the number of canceled tasks
-   */
-  int clear();
-
   /**
    * Removes a task from the queue and registers it to past activities. This method
    * is called by Compute Engine workers when task is processed and can include an option {@link CeTaskResult} object.
index 4468e1ab7afc1a0142ea9f9f55d35274a5c1f62c..13a424cba7d96a3d7a0bbe994bebe798260aff68 100644 (file)
@@ -103,11 +103,6 @@ public class InternalCeQueueImpl extends CeQueueImpl implements InternalCeQueue
     }
   }
 
-  @Override
-  public int clear() {
-    return cancelAll(true);
-  }
-
   @Override
   public void remove(CeTask task, CeActivityDto.Status status, @Nullable CeTaskResult taskResult, @Nullable Throwable error) {
     checkArgument(error == null || status == CeActivityDto.Status.FAILED, "Error can be provided only when status is FAILED");
index e75d0215a3df49ee6088160321d47ecaa888365e..68361fe5b647de2002a4452a5e890a623889c65a 100644 (file)
@@ -123,7 +123,7 @@ public class ComputeEngineContainerImplTest {
       assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize(
         CONTAINER_ITSELF
           + 25 // MigrationConfigurationModule
-          + 17 // level 2
+          + 16 // level 2
       );
       assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
         COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/queue/CeQueueCleanerTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/queue/CeQueueCleanerTest.java
deleted file mode 100644 (file)
index 771b13c..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.ce.queue;
-
-import java.util.Optional;
-import org.apache.commons.io.IOUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.ce.CeQueueDto;
-import org.sonar.db.ce.CeTaskInputDao;
-import org.sonar.db.ce.CeTaskTypes;
-import org.sonar.process.ProcessProperties;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class CeQueueCleanerTest {
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private ServerUpgradeStatus serverUpgradeStatus = mock(ServerUpgradeStatus.class);
-  private InternalCeQueue queue = mock(InternalCeQueue.class);
-  private MapSettings settings = new MapSettings();
-
-  @Test
-  public void start_does_not_reset_in_progress_tasks_to_pending() {
-    insertInQueue("TASK_1", CeQueueDto.Status.PENDING);
-    insertInQueue("TASK_2", CeQueueDto.Status.IN_PROGRESS);
-
-    runCleaner();
-
-    assertThat(dbTester.getDbClient().ceQueueDao().countByStatus(dbTester.getSession(), CeQueueDto.Status.PENDING)).isEqualTo(1);
-    assertThat(dbTester.getDbClient().ceQueueDao().countByStatus(dbTester.getSession(), CeQueueDto.Status.IN_PROGRESS)).isEqualTo(1);
-  }
-
-  @Test
-  public void start_clears_queue_if_version_upgrade() {
-    when(serverUpgradeStatus.isUpgraded()).thenReturn(true);
-
-    runCleaner();
-
-    verify(queue).clear();
-  }
-
-  @Test
-  public void start_does_not_clear_queue_if_version_upgrade_but_blue_green_deployment() {
-    when(serverUpgradeStatus.isUpgraded()).thenReturn(true);
-    settings.setProperty(ProcessProperties.Property.BLUE_GREEN_ENABLED.getKey(), true);
-
-    runCleaner();
-
-    verify(queue, never()).clear();
-  }
-
-  @Test
-  public void start_deletes_orphan_report_files() {
-    // analysis reports are persisted but the associated
-    // task is not in the queue
-    insertInQueue("TASK_1", CeQueueDto.Status.PENDING);
-    insertTaskData("TASK_1");
-    insertTaskData("TASK_2");
-
-    runCleaner();
-
-    CeTaskInputDao dataDao = dbTester.getDbClient().ceTaskInputDao();
-    Optional<CeTaskInputDao.DataStream> task1Data = dataDao.selectData(dbTester.getSession(), "TASK_1");
-    assertThat(task1Data).isPresent();
-    task1Data.get().close();
-
-    assertThat(dataDao.selectData(dbTester.getSession(), "TASK_2")).isNotPresent();
-  }
-
-  private CeQueueDto insertInQueue(String taskUuid, CeQueueDto.Status status) {
-    CeQueueDto dto = new CeQueueDto();
-    dto.setTaskType(CeTaskTypes.REPORT);
-    dto.setComponentUuid("PROJECT_1");
-    dto.setUuid(taskUuid);
-    dto.setStatus(status);
-    dbTester.getDbClient().ceQueueDao().insert(dbTester.getSession(), dto);
-    dbTester.getSession().commit();
-    return dto;
-  }
-
-  private void insertTaskData(String taskUuid) {
-    dbTester.getDbClient().ceTaskInputDao().insert(dbTester.getSession(), taskUuid, IOUtils.toInputStream("{binary}"));
-    dbTester.getSession().commit();
-  }
-
-  private void runCleaner() {
-    CeQueueCleaner cleaner = new CeQueueCleaner(dbTester.getDbClient(), serverUpgradeStatus, queue, settings.asConfig());
-    cleaner.start();
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java b/server/sonar-server-common/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java
deleted file mode 100644 (file)
index 6948c10..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.platform;
-
-import java.util.Optional;
-import org.apache.commons.lang.builder.ReflectionToStringBuilder;
-import org.apache.commons.lang.builder.ToStringStyle;
-import org.picocontainer.Startable;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.process.ProcessProperties;
-import org.sonar.server.platform.db.migration.step.MigrationSteps;
-import org.sonar.server.platform.db.migration.version.DatabaseVersion;
-
-public class DefaultServerUpgradeStatus implements ServerUpgradeStatus, Startable {
-
-  private final DatabaseVersion dbVersion;
-  private final MigrationSteps migrationSteps;
-  private final Configuration configuration;
-
-  // available when connected to db
-  private long initialDbVersion;
-
-  public DefaultServerUpgradeStatus(DatabaseVersion dbVersion, MigrationSteps migrationSteps, Configuration configuration) {
-    this.dbVersion = dbVersion;
-    this.migrationSteps = migrationSteps;
-    this.configuration = configuration;
-  }
-
-  @Override
-  public void start() {
-    Optional<Long> v = dbVersion.getVersion();
-    this.initialDbVersion = v.orElse(-1L);
-  }
-
-  @Override
-  public void stop() {
-    // do nothing
-  }
-
-  @Override
-  public boolean isUpgraded() {
-    return !isFreshInstall() && (initialDbVersion < migrationSteps.getMaxMigrationNumber());
-  }
-
-  @Override
-  public boolean isFreshInstall() {
-    return initialDbVersion < 0;
-  }
-
-  @Override
-  public int getInitialDbVersion() {
-    return (int) initialDbVersion;
-  }
-
-  public boolean isBlueGreen() {
-    return configuration.getBoolean(ProcessProperties.Property.BLUE_GREEN_ENABLED.getKey()).orElse(false);
-  }
-
-  @Override
-  public String toString() {
-    return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java
deleted file mode 100644 (file)
index 050fc56..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.platform;
-
-import java.util.Optional;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.config.internal.ConfigurationBridge;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.server.platform.db.migration.step.MigrationSteps;
-import org.sonar.server.platform.db.migration.version.DatabaseVersion;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class DefaultServerUpgradeStatusTest {
-  private static final long LAST_VERSION = 150;
-  private MigrationSteps migrationSteps = mock(MigrationSteps.class);
-  private DatabaseVersion dbVersion = mock(DatabaseVersion.class);
-  private MapSettings settings = new MapSettings();
-  private DefaultServerUpgradeStatus underTest = new DefaultServerUpgradeStatus(dbVersion, migrationSteps, new ConfigurationBridge(settings));
-
-  @Before
-  public void setUp() throws Exception {
-    when(migrationSteps.getMaxMigrationNumber()).thenReturn(LAST_VERSION);
-  }
-
-  @Test
-  public void shouldBeFreshInstallation() {
-    when(migrationSteps.getMaxMigrationNumber()).thenReturn(150L);
-    when(dbVersion.getVersion()).thenReturn(Optional.empty());
-
-    underTest.start();
-
-    assertThat(underTest.isFreshInstall()).isTrue();
-    assertThat(underTest.isUpgraded()).isFalse();
-    assertThat(underTest.getInitialDbVersion()).isEqualTo(-1);
-  }
-
-  @Test
-  public void shouldBeUpgraded() {
-    when(dbVersion.getVersion()).thenReturn(Optional.of(50L));
-
-    underTest.start();
-
-    assertThat(underTest.isFreshInstall()).isFalse();
-    assertThat(underTest.isUpgraded()).isTrue();
-    assertThat(underTest.getInitialDbVersion()).isEqualTo(50);
-  }
-
-  @Test
-  public void shouldNotBeUpgraded() {
-    when(dbVersion.getVersion()).thenReturn(Optional.of(LAST_VERSION));
-
-    underTest.start();
-
-    assertThat(underTest.isFreshInstall()).isFalse();
-    assertThat(underTest.isUpgraded()).isFalse();
-    assertThat(underTest.getInitialDbVersion()).isEqualTo((int) LAST_VERSION);
-  }
-
-  @Test
-  public void isBlueGreen() {
-    settings.clear();
-    assertThat(underTest.isBlueGreen()).isFalse();
-
-    settings.setProperty("sonar.blueGreenEnabled", true);
-    assertThat(underTest.isBlueGreen()).isTrue();
-
-    settings.setProperty("sonar.blueGreenEnabled", false);
-    assertThat(underTest.isBlueGreen()).isFalse();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/queue/CeQueueCleaner.java b/server/sonar-server/src/main/java/org/sonar/server/ce/queue/CeQueueCleaner.java
new file mode 100644 (file)
index 0000000..260f24b
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.queue;
+
+import java.util.List;
+import org.picocontainer.Startable;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.ce.queue.CeQueue;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.process.ProcessProperties;
+
+/**
+ * Cleans-up the Compute Engine queue.
+ */
+@ServerSide
+public class CeQueueCleaner implements Startable {
+
+  private static final Logger LOGGER = Loggers.get(CeQueueCleaner.class);
+
+  private final DbClient dbClient;
+  private final ServerUpgradeStatus serverUpgradeStatus;
+  private final CeQueue queue;
+  private final Configuration configuration;
+
+  public CeQueueCleaner(DbClient dbClient, ServerUpgradeStatus serverUpgradeStatus, CeQueue queue, Configuration configuration) {
+    this.dbClient = dbClient;
+    this.serverUpgradeStatus = serverUpgradeStatus;
+    this.queue = queue;
+    this.configuration = configuration;
+  }
+
+  @Override
+  public void start() {
+    if (serverUpgradeStatus.isUpgraded() && !isBlueGreenDeployment()) {
+      cleanOnUpgrade();
+    }
+    cleanUpTaskInputOrphans();
+  }
+
+  private boolean isBlueGreenDeployment() {
+    return configuration.getBoolean(ProcessProperties.Property.BLUE_GREEN_ENABLED.getKey()).orElse(false);
+  }
+
+  private void cleanOnUpgrade() {
+    // we assume that pending tasks are not compatible with the new version
+    // and can't be processed
+    LOGGER.info("Cancel all pending tasks (due to upgrade)");
+    queue.clear();
+  }
+
+  private void cleanUpTaskInputOrphans() {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      // Reports that have been processed are not kept in database yet.
+      // They are supposed to be systematically dropped.
+      // Let's clean-up orphans if any.
+      List<String> uuids = dbClient.ceTaskInputDao().selectUuidsNotInQueue(dbSession);
+      dbClient.ceTaskInputDao().deleteByUuids(dbSession, uuids);
+      dbSession.commit();
+    }
+  }
+
+  @Override
+  public void stop() {
+    // nothing to do
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java b/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerUpgradeStatus.java
new file mode 100644 (file)
index 0000000..6948c10
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.platform;
+
+import java.util.Optional;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.picocontainer.Startable;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.process.ProcessProperties;
+import org.sonar.server.platform.db.migration.step.MigrationSteps;
+import org.sonar.server.platform.db.migration.version.DatabaseVersion;
+
+public class DefaultServerUpgradeStatus implements ServerUpgradeStatus, Startable {
+
+  private final DatabaseVersion dbVersion;
+  private final MigrationSteps migrationSteps;
+  private final Configuration configuration;
+
+  // available when connected to db
+  private long initialDbVersion;
+
+  public DefaultServerUpgradeStatus(DatabaseVersion dbVersion, MigrationSteps migrationSteps, Configuration configuration) {
+    this.dbVersion = dbVersion;
+    this.migrationSteps = migrationSteps;
+    this.configuration = configuration;
+  }
+
+  @Override
+  public void start() {
+    Optional<Long> v = dbVersion.getVersion();
+    this.initialDbVersion = v.orElse(-1L);
+  }
+
+  @Override
+  public void stop() {
+    // do nothing
+  }
+
+  @Override
+  public boolean isUpgraded() {
+    return !isFreshInstall() && (initialDbVersion < migrationSteps.getMaxMigrationNumber());
+  }
+
+  @Override
+  public boolean isFreshInstall() {
+    return initialDbVersion < 0;
+  }
+
+  @Override
+  public int getInitialDbVersion() {
+    return (int) initialDbVersion;
+  }
+
+  public boolean isBlueGreen() {
+    return configuration.getBoolean(ProcessProperties.Property.BLUE_GREEN_ENABLED.getKey()).orElse(false);
+  }
+
+  @Override
+  public String toString() {
+    return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
+  }
+}
index 38c80290b2a9c33baa2cc021272f9b0568d7388e..1fb52415beb4cd804998597e75c4375dbcf01b60 100644 (file)
@@ -23,6 +23,7 @@ import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.platform.EditionProvider;
 import org.sonar.core.platform.PlatformEditionProvider;
 import org.sonar.server.app.ProcessCommandWrapper;
+import org.sonar.server.ce.queue.CeQueueCleaner;
 import org.sonar.server.es.IndexerStartupTask;
 import org.sonar.server.organization.DefaultOrganizationEnforcer;
 import org.sonar.server.platform.ServerLifecycleNotifier;
@@ -68,7 +69,8 @@ public class PlatformLevelStartup extends PlatformLevel {
       BuiltInQProfileUpdateImpl.class,
       RegisterQualityProfiles.class,
       RegisterPermissionTemplates.class,
-      RenameDeprecatedPropertyKeys.class);
+      RenameDeprecatedPropertyKeys.class,
+      CeQueueCleaner.class);
 
     // RegisterServletFilters makes the WebService engine of Level4 served by the MasterServletFilter, therefor it
     // must be started after all the other startup tasks
diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/queue/CeQueueCleanerTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/queue/CeQueueCleanerTest.java
new file mode 100644 (file)
index 0000000..f8c78ae
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.queue;
+
+import java.util.Optional;
+import org.apache.commons.io.IOUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.queue.CeQueue;
+import org.sonar.db.DbTester;
+import org.sonar.db.ce.CeQueueDto;
+import org.sonar.db.ce.CeTaskInputDao;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.process.ProcessProperties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CeQueueCleanerTest {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private ServerUpgradeStatus serverUpgradeStatus = mock(ServerUpgradeStatus.class);
+  private CeQueue queue = mock(CeQueue.class);
+  private MapSettings settings = new MapSettings();
+
+  @Test
+  public void start_does_not_reset_in_progress_tasks_to_pending() {
+    insertInQueue("TASK_1", CeQueueDto.Status.PENDING);
+    insertInQueue("TASK_2", CeQueueDto.Status.IN_PROGRESS);
+
+    runCleaner();
+
+    assertThat(dbTester.getDbClient().ceQueueDao().countByStatus(dbTester.getSession(), CeQueueDto.Status.PENDING)).isEqualTo(1);
+    assertThat(dbTester.getDbClient().ceQueueDao().countByStatus(dbTester.getSession(), CeQueueDto.Status.IN_PROGRESS)).isEqualTo(1);
+  }
+
+  @Test
+  public void start_clears_queue_if_version_upgrade() {
+    when(serverUpgradeStatus.isUpgraded()).thenReturn(true);
+
+    runCleaner();
+
+    verify(queue).clear();
+  }
+
+  @Test
+  public void start_does_not_clear_queue_if_version_upgrade_but_blue_green_deployment() {
+    when(serverUpgradeStatus.isUpgraded()).thenReturn(true);
+    settings.setProperty(ProcessProperties.Property.BLUE_GREEN_ENABLED.getKey(), true);
+
+    runCleaner();
+
+    verify(queue, never()).clear();
+  }
+
+  @Test
+  public void start_deletes_orphan_report_files() {
+    // analysis reports are persisted but the associated
+    // task is not in the queue
+    insertInQueue("TASK_1", CeQueueDto.Status.PENDING);
+    insertTaskData("TASK_1");
+    insertTaskData("TASK_2");
+
+    runCleaner();
+
+    CeTaskInputDao dataDao = dbTester.getDbClient().ceTaskInputDao();
+    Optional<CeTaskInputDao.DataStream> task1Data = dataDao.selectData(dbTester.getSession(), "TASK_1");
+    assertThat(task1Data).isPresent();
+    task1Data.get().close();
+
+    assertThat(dataDao.selectData(dbTester.getSession(), "TASK_2")).isNotPresent();
+  }
+
+  private CeQueueDto insertInQueue(String taskUuid, CeQueueDto.Status status) {
+    CeQueueDto dto = new CeQueueDto();
+    dto.setTaskType(CeTaskTypes.REPORT);
+    dto.setComponentUuid("PROJECT_1");
+    dto.setUuid(taskUuid);
+    dto.setStatus(status);
+    dbTester.getDbClient().ceQueueDao().insert(dbTester.getSession(), dto);
+    dbTester.getSession().commit();
+    return dto;
+  }
+
+  private void insertTaskData(String taskUuid) {
+    dbTester.getDbClient().ceTaskInputDao().insert(dbTester.getSession(), taskUuid, IOUtils.toInputStream("{binary}"));
+    dbTester.getSession().commit();
+  }
+
+  private void runCleaner() {
+    CeQueueCleaner cleaner = new CeQueueCleaner(dbTester.getDbClient(), serverUpgradeStatus, queue, settings.asConfig());
+    cleaner.start();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerUpgradeStatusTest.java
new file mode 100644 (file)
index 0000000..050fc56
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.platform;
+
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.config.internal.ConfigurationBridge;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.platform.db.migration.step.MigrationSteps;
+import org.sonar.server.platform.db.migration.version.DatabaseVersion;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DefaultServerUpgradeStatusTest {
+  private static final long LAST_VERSION = 150;
+  private MigrationSteps migrationSteps = mock(MigrationSteps.class);
+  private DatabaseVersion dbVersion = mock(DatabaseVersion.class);
+  private MapSettings settings = new MapSettings();
+  private DefaultServerUpgradeStatus underTest = new DefaultServerUpgradeStatus(dbVersion, migrationSteps, new ConfigurationBridge(settings));
+
+  @Before
+  public void setUp() throws Exception {
+    when(migrationSteps.getMaxMigrationNumber()).thenReturn(LAST_VERSION);
+  }
+
+  @Test
+  public void shouldBeFreshInstallation() {
+    when(migrationSteps.getMaxMigrationNumber()).thenReturn(150L);
+    when(dbVersion.getVersion()).thenReturn(Optional.empty());
+
+    underTest.start();
+
+    assertThat(underTest.isFreshInstall()).isTrue();
+    assertThat(underTest.isUpgraded()).isFalse();
+    assertThat(underTest.getInitialDbVersion()).isEqualTo(-1);
+  }
+
+  @Test
+  public void shouldBeUpgraded() {
+    when(dbVersion.getVersion()).thenReturn(Optional.of(50L));
+
+    underTest.start();
+
+    assertThat(underTest.isFreshInstall()).isFalse();
+    assertThat(underTest.isUpgraded()).isTrue();
+    assertThat(underTest.getInitialDbVersion()).isEqualTo(50);
+  }
+
+  @Test
+  public void shouldNotBeUpgraded() {
+    when(dbVersion.getVersion()).thenReturn(Optional.of(LAST_VERSION));
+
+    underTest.start();
+
+    assertThat(underTest.isFreshInstall()).isFalse();
+    assertThat(underTest.isUpgraded()).isFalse();
+    assertThat(underTest.getInitialDbVersion()).isEqualTo((int) LAST_VERSION);
+  }
+
+  @Test
+  public void isBlueGreen() {
+    settings.clear();
+    assertThat(underTest.isBlueGreen()).isFalse();
+
+    settings.setProperty("sonar.blueGreenEnabled", true);
+    assertThat(underTest.isBlueGreen()).isTrue();
+
+    settings.setProperty("sonar.blueGreenEnabled", false);
+    assertThat(underTest.isBlueGreen()).isFalse();
+  }
+}