aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorBelen Pruvost <belen.pruvost@sonarsource.com>2022-07-18 09:35:33 +0200
committersonartech <sonartech@sonarsource.com>2022-07-25 20:03:57 +0000
commit6bb86216fbd218cea009091f32addaeb2cafb555 (patch)
tree6fad06a6f3c74995dc6ed90887af790742c39309 /server
parent9b1a191c91a20d5551c290e8bab6460824f135e5 (diff)
downloadsonarqube-6bb86216fbd218cea009091f32addaeb2cafb555.tar.gz
sonarqube-6bb86216fbd218cea009091f32addaeb2cafb555.zip
SONAR-16645 - Push Events Scheduled Purge
Diffstat (limited to 'server')
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDao.java17
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDto.java7
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventMapper.java7
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/pushevent/PushEventMapper.xml15
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/pushevent/PushEventDaoTest.java47
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushWsModule.java10
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorService.java27
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorServiceImpl.java38
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializer.java41
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeScheduler.java27
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImpl.java98
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorServiceImplTest.java35
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializerTest.java46
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImplTest.java139
14 files changed, 549 insertions, 5 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDao.java
index f8024f8333b..e8db98fd74f 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDao.java
@@ -19,11 +19,14 @@
*/
package org.sonar.db.pushevent;
+import java.util.Set;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactory;
import org.sonar.db.Dao;
import org.sonar.db.DbSession;
+import static org.sonar.db.DatabaseUtils.executeLargeUpdates;
+
public class PushEventDao implements Dao {
private final UuidFactory uuidFactory;
@@ -39,7 +42,10 @@ public class PushEventDao implements Dao {
event.setUuid(uuidFactory.create());
}
- event.setCreatedAt(system2.now());
+ if (event.getCreatedAt() == null) {
+ event.setCreatedAt(system2.now());
+ }
+
mapper(dbSession).insert(event);
return event;
}
@@ -48,6 +54,15 @@ public class PushEventDao implements Dao {
return mapper(dbSession).selectByUuid(uuid);
}
+
+ public Set<String> selectUuidsOfExpiredEvents(DbSession dbSession, long timestamp) {
+ return mapper(dbSession).selectUuidsOfExpiredEvents(timestamp);
+ }
+
+ public void deleteByUuids(DbSession dbSession, Set<String> pushEventUuids) {
+ executeLargeUpdates(pushEventUuids, mapper(dbSession)::deleteByUuids);
+ }
+
private static PushEventMapper mapper(DbSession session) {
return session.getMapper(PushEventMapper.class);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDto.java
index ee648fbfd03..13f867a8a27 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventDto.java
@@ -19,11 +19,13 @@
*/
package org.sonar.db.pushevent;
+import javax.annotation.CheckForNull;
+
public class PushEventDto {
private String uuid;
private String projectUuid;
private byte[] payload;
- private long createdAt;
+ private Long createdAt;
public PushEventDto() {
// nothing to do
@@ -56,7 +58,8 @@ public class PushEventDto {
return this;
}
- public long getCreatedAt() {
+ @CheckForNull
+ public Long getCreatedAt() {
return createdAt;
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventMapper.java
index 83e489148ed..6d9ae0fe4e0 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/pushevent/PushEventMapper.java
@@ -19,7 +19,10 @@
*/
package org.sonar.db.pushevent;
+import java.util.List;
+import java.util.Set;
import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
public interface PushEventMapper {
@@ -28,4 +31,8 @@ public interface PushEventMapper {
@CheckForNull
PushEventDto selectByUuid(String uuid);
+ Set<String> selectUuidsOfExpiredEvents(@Param("timestamp") long timestamp);
+
+ void deleteByUuids(@Param("pushEventUuids") List<String> pushEventUuids);
+
}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/pushevent/PushEventMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/pushevent/PushEventMapper.xml
index fc472afe742..0f395606bd6 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/pushevent/PushEventMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/pushevent/PushEventMapper.xml
@@ -24,7 +24,7 @@
)
</insert>
- <select id="selectByUuid" parameterType="String" resultType="PushEvent">
+ <select id="selectByUuid" parameterType="String" resultType="PushEvent">
SELECT
<include refid="pushEventColumns"/>
FROM push_events pe
@@ -32,4 +32,17 @@
pe.uuid=#{uuid,jdbcType=VARCHAR}
</select>
+ <select id="selectUuidsOfExpiredEvents" parameterType="long" resultType="string">
+ SELECT
+ pe.uuid
+ FROM push_events pe
+ where
+ pe.created_at &lt;= #{timestamp,jdbcType=BIGINT}
+ </select>
+
+ <delete id="deleteByUuids" parameterType="String">
+ delete from push_events
+ where uuid in <foreach collection="pushEventUuids" open="(" close=")" item="uuid" separator=",">#{uuid,jdbcType=VARCHAR}</foreach>
+ </delete>
+
</mapper>
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/pushevent/PushEventDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/pushevent/PushEventDaoTest.java
index 1ca9bf2ad58..e72ab4d4b28 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/pushevent/PushEventDaoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/pushevent/PushEventDaoTest.java
@@ -19,6 +19,7 @@
*/
package org.sonar.db.pushevent;
+import java.util.Set;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.impl.utils.TestSystem2;
@@ -67,4 +68,50 @@ public class PushEventDaoTest {
}
+ @Test
+ public void select_expired_events() {
+ PushEventDto eventDtoFirst = new PushEventDto()
+ .setProjectUuid("project-uuid")
+ .setCreatedAt(1000L)
+ .setPayload("some-event".getBytes(UTF_8));
+
+ PushEventDto eventDtoSecond = new PushEventDto()
+ .setProjectUuid("project-uuid")
+ .setCreatedAt(1000L)
+ .setPayload("some-event".getBytes(UTF_8));
+
+ PushEventDto eventDtoThird = new PushEventDto()
+ .setProjectUuid("project-uuid")
+ .setCreatedAt(2000L)
+ .setPayload("some-event".getBytes(UTF_8));
+
+ underTest.insert(session, eventDtoFirst);
+ underTest.insert(session, eventDtoSecond);
+ underTest.insert(session, eventDtoThird);
+
+ assertThat(underTest.selectUuidsOfExpiredEvents(session, 2000L)).hasSize(3);
+ assertThat(underTest.selectUuidsOfExpiredEvents(session, 1500L)).hasSize(2);
+ assertThat(underTest.selectUuidsOfExpiredEvents(session, 150L)).isEmpty();
+ }
+
+ @Test
+ public void delete_events_in_batches() {
+ PushEventDto eventDtoFirst = new PushEventDto()
+ .setProjectUuid("project-uuid")
+ .setCreatedAt(1000L)
+ .setPayload("some-event".getBytes(UTF_8));
+
+ PushEventDto eventDtoSecond = new PushEventDto()
+ .setProjectUuid("project-uuid")
+ .setCreatedAt(1000L)
+ .setPayload("some-event".getBytes(UTF_8));
+
+ PushEventDto event1 = underTest.insert(session, eventDtoFirst);
+ PushEventDto event2 = underTest.insert(session, eventDtoSecond);
+
+ assertThat(underTest.selectUuidsOfExpiredEvents(db.getSession(), 2000L)).hasSize(2);
+ underTest.deleteByUuids(db.getSession(), Set.of(event1.getUuid(), event2.getUuid()));
+ assertThat(underTest.selectUuidsOfExpiredEvents(db.getSession(), 2000L)).isEmpty();
+ }
+
}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushWsModule.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushWsModule.java
index 0a48485466c..091b56d7d56 100644
--- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushWsModule.java
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushWsModule.java
@@ -20,6 +20,9 @@
package org.sonar.server.pushapi;
import org.sonar.core.platform.Module;
+import org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeExecutorServiceImpl;
+import org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeInitializer;
+import org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeSchedulerImpl;
import org.sonar.server.pushapi.sonarlint.SonarLintClientPermissionsValidator;
import org.sonar.server.pushapi.sonarlint.SonarLintClientsRegistry;
import org.sonar.server.pushapi.sonarlint.SonarLintPushAction;
@@ -32,6 +35,11 @@ public class ServerPushWsModule extends Module {
ServerPushWs.class,
SonarLintClientPermissionsValidator.class,
SonarLintClientsRegistry.class,
- SonarLintPushAction.class);
+ SonarLintPushAction.class,
+
+ // Push Events Purge
+ PushEventsPurgeSchedulerImpl.class,
+ PushEventsPurgeExecutorServiceImpl.class,
+ PushEventsPurgeInitializer.class);
}
}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorService.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorService.java
new file mode 100644
index 00000000000..39a1024450d
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorService.java
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.scheduler.purge;
+
+import java.util.concurrent.ScheduledExecutorService;
+import org.sonar.api.server.ServerSide;
+
+@ServerSide
+public interface PushEventsPurgeExecutorService extends ScheduledExecutorService {
+}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorServiceImpl.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorServiceImpl.java
new file mode 100644
index 00000000000..1e1629f40d6
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorServiceImpl.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.scheduler.purge;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import org.sonar.server.util.AbstractStoppableScheduledExecutorServiceImpl;
+
+public class PushEventsPurgeExecutorServiceImpl
+ extends AbstractStoppableScheduledExecutorServiceImpl<ScheduledExecutorService>
+ implements PushEventsPurgeExecutorService {
+
+ public PushEventsPurgeExecutorServiceImpl() {
+ super(Executors.newSingleThreadScheduledExecutor(r -> {
+ Thread thread = Executors.defaultThreadFactory().newThread(r);
+ thread.setDaemon(true);
+ thread.setName(String.format("PushEvents-Purge-%d", System.nanoTime()));
+ return thread;
+ }));
+ }
+}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializer.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializer.java
new file mode 100644
index 00000000000..1abea71ea67
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializer.java
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.scheduler.purge;
+
+import javax.annotation.Nullable;
+import org.sonar.api.platform.Server;
+import org.sonar.api.platform.ServerStartHandler;
+
+public class PushEventsPurgeInitializer implements ServerStartHandler {
+
+ private final PushEventsPurgeScheduler pushEventsPurgeScheduler;
+
+ public PushEventsPurgeInitializer(@Nullable PushEventsPurgeScheduler pushEventsPurgeScheduler) {
+ this.pushEventsPurgeScheduler = pushEventsPurgeScheduler;
+ }
+
+ @Override
+ public void onServerStart(Server server) {
+ if (pushEventsPurgeScheduler != null) {
+ pushEventsPurgeScheduler.startScheduling();
+ }
+ }
+
+}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeScheduler.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeScheduler.java
new file mode 100644
index 00000000000..d6ce454f753
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeScheduler.java
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.scheduler.purge;
+
+import org.sonar.api.server.ServerSide;
+
+@ServerSide
+public interface PushEventsPurgeScheduler {
+ void startScheduling();
+}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImpl.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImpl.java
new file mode 100644
index 00000000000..3a72765bbcb
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImpl.java
@@ -0,0 +1,98 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.scheduler.purge;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Set;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.System2;
+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.server.util.GlobalLockManager;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+public class PushEventsPurgeSchedulerImpl implements PushEventsPurgeScheduler {
+ private static final Logger LOG = Loggers.get(PushEventsPurgeSchedulerImpl.class);
+ private static final String LOCK_NAME = "PushEventsPurgeCheck";
+ private static final String INITIAL_DELAY_IN_SECONDS = "sonar.push.events.purge.initial.delay";
+ private static final String ENQUEUE_DELAY_IN_SECONDS = "sonar.push.events.purge.enqueue.delay";
+ private static final int ENQUEUE_LOCK_DELAY_IN_SECONDS = 60;
+
+ private final DbClient dbClient;
+ private final Configuration config;
+ private final GlobalLockManager lockManager;
+ private final PushEventsPurgeExecutorService executorService;
+ private final System2 system;
+
+
+ public PushEventsPurgeSchedulerImpl(DbClient dbClient, Configuration config, GlobalLockManager lockManager,
+ PushEventsPurgeExecutorService executorService, System2 system) {
+ this.dbClient = dbClient;
+ this.executorService = executorService;
+ this.config = config;
+ this.lockManager = lockManager;
+ this.system = system;
+ }
+
+ @Override
+ public void startScheduling() {
+ executorService.scheduleAtFixedRate(this::checks, getInitialDelay(),
+ getEnqueueDelay(), SECONDS);
+ }
+
+ private void checks() {
+ try {
+ // Avoid enqueueing push events purge task multiple times
+ if (!lockManager.tryLock(LOCK_NAME, ENQUEUE_LOCK_DELAY_IN_SECONDS)) {
+ return;
+ }
+ purgeExpiredPushEvents();
+ } catch (Exception e) {
+ LOG.error("Error in Push Events Purge scheduler", e);
+ }
+ }
+
+ private void purgeExpiredPushEvents() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ Set<String> uuids = dbClient.pushEventDao().selectUuidsOfExpiredEvents(dbSession, getExpiredTimestamp());
+ LOG.debug(String.format("%s push events to be deleted...", uuids.size()));
+ dbClient.pushEventDao().deleteByUuids(dbSession, uuids);
+ dbSession.commit();
+ }
+ }
+
+ public long getInitialDelay() {
+ return config.getLong(INITIAL_DELAY_IN_SECONDS).orElse(60 * 60L);
+ }
+
+ public long getEnqueueDelay() {
+ return config.getLong(ENQUEUE_DELAY_IN_SECONDS).orElse(60 * 60L);
+ }
+
+ private long getExpiredTimestamp() {
+ return Instant.ofEpochMilli(system.now())
+ .minus(1, ChronoUnit.HOURS)
+ .toEpochMilli();
+ }
+}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorServiceImplTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorServiceImplTest.java
new file mode 100644
index 00000000000..4345299d028
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeExecutorServiceImplTest.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.scheduler.purge;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PushEventsPurgeExecutorServiceImplTest {
+
+ @Test
+ public void constructor_createsProperDeleagateThatIsReadyToAct() {
+ PushEventsPurgeExecutorServiceImpl pushEventsPurgeExecutorService = new PushEventsPurgeExecutorServiceImpl();
+
+ assertThat(pushEventsPurgeExecutorService.isShutdown()).isFalse();
+ assertThat(pushEventsPurgeExecutorService.isTerminated()).isFalse();
+ }
+}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializerTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializerTest.java
new file mode 100644
index 00000000000..7fa2a006091
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializerTest.java
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.scheduler.purge;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class PushEventsPurgeInitializerTest {
+
+ private PushEventsPurgeScheduler pushEventsPurgeScheduler;
+
+ @Before
+ public void before() {
+ pushEventsPurgeScheduler = mock(PushEventsPurgeScheduler.class);
+ }
+
+ @Test
+ public void onServerStart_givenProjectReportScheduler_startScheduling() {
+ PushEventsPurgeInitializer pushEventsPurgeInitializer = new PushEventsPurgeInitializer(pushEventsPurgeScheduler);
+
+ pushEventsPurgeInitializer.onServerStart(null);
+
+ verify(pushEventsPurgeScheduler, times(1)).startScheduling();
+ }
+}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImplTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImplTest.java
new file mode 100644
index 00000000000..4e05dd2abf7
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImplTest.java
@@ -0,0 +1,139 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.scheduler.purge;
+
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.pushevent.PushEventDao;
+import org.sonar.server.util.AbstractStoppableExecutorService;
+import org.sonar.server.util.GlobalLockManager;
+import org.sonar.server.util.GlobalLockManagerImpl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+public class PushEventsPurgeSchedulerImplTest {
+ private final DbClient dbClient = mock(DbClient.class);
+ private final DbSession dbSession = mock(DbSession.class);
+ private final GlobalLockManager lockManager = mock(GlobalLockManagerImpl.class);
+ private final PushEventDao pushEventDao = mock(PushEventDao.class);
+ private final PushEventsPurgeExecutorServiceImpl executorService = new PushEventsPurgeExecutorServiceImpl();
+ private final Configuration configuration = mock(Configuration.class);
+ private final System2 system2 = mock(System2.class);
+ private final PushEventsPurgeSchedulerImpl underTest = new PushEventsPurgeSchedulerImpl(dbClient, configuration,
+ lockManager, executorService, system2);
+
+ @Before
+ public void prepare() {
+ when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
+ }
+
+ @Test
+ public void doNothingIfLocked() {
+ when(lockManager.tryLock(any(), anyInt())).thenReturn(false);
+
+ underTest.startScheduling();
+
+ executorService.runCommand();
+
+ verifyNoInteractions(dbClient);
+ }
+
+ @Test
+ public void doNothingIfExceptionIsThrown() {
+ when(lockManager.tryLock(any(), anyInt())).thenThrow(new IllegalArgumentException("Oops"));
+
+ underTest.startScheduling();
+
+ executorService.runCommand();
+
+ verifyNoInteractions(dbClient);
+ }
+
+ @Test
+ public void schedulePurgeTaskWhenNotLocked() {
+ when(system2.now()).thenReturn(100000000L);
+ when(dbClient.pushEventDao()).thenReturn(pushEventDao);
+ when(dbClient.openSession(false)).thenReturn(dbSession);
+ when(dbClient.pushEventDao().selectUuidsOfExpiredEvents(any(), anyLong())).thenReturn(Set.of("1", "2"));
+
+ underTest.startScheduling();
+
+ executorService.runCommand();
+
+ ArgumentCaptor<Set> uuidsCaptor = ArgumentCaptor.forClass(Set.class);
+
+ verify(pushEventDao).deleteByUuids(any(), uuidsCaptor.capture());
+ Set<String> uuids = uuidsCaptor.getValue();
+ assertThat(uuids).containsExactlyInAnyOrder("1", "2");
+ }
+
+ private static class PushEventsPurgeExecutorServiceImpl extends AbstractStoppableExecutorService<ScheduledExecutorService>
+ implements PushEventsPurgeExecutorService {
+
+ private Runnable command;
+
+ public PushEventsPurgeExecutorServiceImpl() {
+ super(null);
+ }
+
+ public void runCommand() {
+ command.run();
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
+ this.command = command;
+ return null;
+ }
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ }
+}