Merge branch 'stable-3.10' into stable-3.11
* stable-3.10:
FetchOne: consider order of create/delete events when replicating deletions
Consume only relevant stream events
Use stream events to delete repositories
Change-Id: Ib3318cee50d8c43a3ff44bf5f50c2b73174a7c73
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
index e75c4c3..5e7fda2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
@@ -17,6 +17,7 @@
import static com.googlesource.gerrit.plugins.replication.pull.PullReplicationLogger.repLog;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
@@ -476,11 +477,7 @@
try {
List<FetchRefSpec> toFetch =
fetchRefSpecs.stream().filter(rs -> rs.getSource() != null).toList();
- Set<String> toDelete =
- fetchRefSpecs.stream()
- .filter(rs -> rs.getSource() == null)
- .map(RefSpec::getDestination)
- .collect(Collectors.toSet());
+ Set<String> toDelete = refsToDelete(fetchRefSpecs);
updateStates(fetch.fetch(toFetch));
// JGit doesn't support a fetch of <empty> to a ref (e.g. :refs/to/delete) therefore we have
@@ -512,6 +509,21 @@
return fetchRefSpecs;
}
+ @VisibleForTesting
+ static Set<String> refsToDelete(List<FetchRefSpec> fetchRefSpecs) {
+ final Set<String> refsToDelete = new HashSet<>();
+ fetchRefSpecs.forEach(
+ rs -> {
+ String refName = rs.refName();
+ if (rs.isDelete()) {
+ refsToDelete.add(refName);
+ } else {
+ refsToDelete.remove(refName);
+ }
+ });
+ return refsToDelete;
+ }
+
/**
* Return the list of refSpecs to fetch, possibly after having been filtered.
*
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchRefSpec.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchRefSpec.java
index f85f22a..e41e194 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchRefSpec.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchRefSpec.java
@@ -44,6 +44,10 @@
return MoreObjects.firstNonNull(getSource(), getDestination());
}
+ public boolean isDelete() {
+ return getSource() == null;
+ }
+
@Override
public String toString() {
return getSource() == null ? "<<DELETED>>:" + getDestination() : super.toString();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java
index f9165ad..18574fd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java
@@ -29,6 +29,7 @@
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.deleteproject.cache.CacheDeleteHandler;
import com.googlesource.gerrit.plugins.deleteproject.fs.RepositoryDelete;
import com.googlesource.gerrit.plugins.replication.pull.GerritConfigOps;
@@ -37,12 +38,13 @@
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.transport.URIish;
-class ProjectDeletionAction
+@Singleton
+public class ProjectDeletionAction
implements RestModifyView<ProjectResource, ProjectDeletionAction.DeleteInput> {
private static final PluginPermission DELETE_PROJECT =
new PluginPermission("delete-project", "deleteProject");
- static class DeleteInput {}
+ public static class DeleteInput {}
private final Provider<CurrentUser> userProvider;
private final GerritConfigOps gerritConfigOps;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
index f863148..eb2d7e0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
@@ -25,7 +25,9 @@
import com.google.gerrit.entities.Project.NameKey;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.config.GerritInstanceId;
import com.google.gerrit.server.data.RefUpdateAttribute;
@@ -37,9 +39,12 @@
import com.google.gerrit.server.events.RefUpdatedEvent;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.restapi.project.ProjectsCollection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;
+import com.googlesource.gerrit.plugins.deleteproject.ProjectDeletedEvent;
import com.googlesource.gerrit.plugins.replication.pull.ApplyObjectsCacheKey;
import com.googlesource.gerrit.plugins.replication.pull.FetchOne;
import com.googlesource.gerrit.plugins.replication.pull.Source;
@@ -47,6 +52,7 @@
import com.googlesource.gerrit.plugins.replication.pull.api.FetchAction;
import com.googlesource.gerrit.plugins.replication.pull.api.FetchJob;
import com.googlesource.gerrit.plugins.replication.pull.api.FetchJob.Factory;
+import com.googlesource.gerrit.plugins.replication.pull.api.ProjectDeletionAction;
import com.googlesource.gerrit.plugins.replication.pull.api.ProjectInitializationAction;
import com.googlesource.gerrit.plugins.replication.pull.api.PullReplicationApiRequestMetrics;
import com.googlesource.gerrit.plugins.replication.pull.api.UpdateHeadCommand;
@@ -68,6 +74,8 @@
private final String instanceId;
private final WorkQueue workQueue;
private final Cache<ApplyObjectsCacheKey, Long> refUpdatesSucceededCache;
+ private final ProjectDeletionAction projectDeletionAction;
+ private final ProjectsCollection projectsCollection;
@Inject
public StreamEventListener(
@@ -79,7 +87,9 @@
Provider<PullReplicationApiRequestMetrics> metricsProvider,
SourcesCollection sources,
ExcludedRefsFilter excludedRefsFilter,
- @Named(APPLY_OBJECTS_CACHE) Cache<ApplyObjectsCacheKey, Long> refUpdatesSucceededCache) {
+ @Named(APPLY_OBJECTS_CACHE) Cache<ApplyObjectsCacheKey, Long> refUpdatesSucceededCache,
+ ProjectDeletionAction projectDeletionAction,
+ ProjectsCollection projectsCollection) {
this.instanceId = instanceId;
this.updateHeadCommand = updateHeadCommand;
this.projectInitializationAction = projectInitializationAction;
@@ -89,6 +99,8 @@
this.sources = sources;
this.refsFilter = excludedRefsFilter;
this.refUpdatesSucceededCache = refUpdatesSucceededCache;
+ this.projectDeletionAction = projectDeletionAction;
+ this.projectsCollection = projectsCollection;
requireNonNull(
Strings.emptyToNull(this.instanceId), "gerrit.instanceId cannot be null or empty");
@@ -173,6 +185,25 @@
"Failed to update HEAD on project: %s", headUpdatedEvent.projectName);
throw e;
}
+ } else if (event instanceof ProjectDeletedEvent) {
+ deleteProject((ProjectDeletedEvent) event);
+ }
+ }
+
+ protected void deleteProject(ProjectEvent projectDeletedEvent) {
+ try {
+ ProjectResource projectResource =
+ projectsCollection.parse(
+ TopLevelResource.INSTANCE,
+ IdString.fromDecoded(projectDeletedEvent.getProjectNameKey().get()));
+ projectDeletionAction.apply(projectResource, new ProjectDeletionAction.DeleteInput());
+ } catch (ResourceNotFoundException e) {
+ logger.atFine().withCause(e).log(
+ "Repository not found whilst trying to delete project:%s",
+ projectDeletedEvent.getProjectNameKey().get());
+ } catch (Exception e) {
+ logger.atSevere().withCause(e).log(
+ "Cannot delete project:%s", projectDeletedEvent.getProjectNameKey().get());
}
}
@@ -181,7 +212,7 @@
}
private boolean shouldReplicateProject(Event event) {
- if (!(event instanceof ProjectEvent)) {
+ if (!isInterestingEventType(event)) {
return false;
}
@@ -202,10 +233,23 @@
&& source.wouldCreateProject(projectCreatedEvent.getProjectNameKey());
}
+ if (event instanceof ProjectDeletedEvent) {
+ ProjectDeletedEvent projectDeletedEvent = (ProjectDeletedEvent) event;
+
+ return source.wouldDeleteProject(projectDeletedEvent.getProjectNameKey());
+ }
+
ProjectEvent projectEvent = (ProjectEvent) event;
return source.wouldFetchProject(projectEvent.getProjectNameKey());
}
+ private static boolean isInterestingEventType(Event event) {
+ return event instanceof ProjectDeletedEvent
+ || event instanceof ProjectCreatedEvent
+ || event instanceof RefUpdatedEvent
+ || event instanceof ProjectHeadUpdatedEvent;
+ }
+
private boolean isRefDelete(RefUpdatedEvent event) {
return ZERO_ID_NAME.equals(event.refUpdate.get().newRev);
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
index 49e8d6f..73715d6 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
@@ -15,6 +15,7 @@
package com.googlesource.gerrit.plugins.replication.pull;
import static com.google.common.truth.Truth.assertThat;
+import static com.googlesource.gerrit.plugins.replication.pull.FetchOne.refsToDelete;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.anyList;
import static org.mockito.Mockito.argThat;
@@ -850,6 +851,36 @@
assertThat(testMetricMaker.getTimer("replication_latency")).isGreaterThan(0);
}
+ @Test
+ public void shouldSkipDeletionWhenDeleteAndCreateOfSameRef() {
+ List<FetchRefSpec> fetchRefSpecs =
+ List.of(
+ FetchRefSpec.fromRef(":refs/something/someref"),
+ FetchRefSpec.fromRef("refs/something/someref"));
+ assertThat(refsToDelete(fetchRefSpecs)).isEmpty();
+ }
+
+ @Test
+ public void shouldDeleteWhenCreateAndDeleteOfSameRef() {
+ List<FetchRefSpec> fetchRefSpecs =
+ List.of(
+ FetchRefSpec.fromRef("refs/something/someref"),
+ FetchRefSpec.fromRef(":refs/something/someref"));
+ assertThat(refsToDelete(fetchRefSpecs)).isEqualTo(Set.of("refs/something/someref"));
+ }
+
+ @Test
+ public void shouldNotDeleteWhenCreateRef() {
+ List<FetchRefSpec> fetchRefSpecs = List.of(FetchRefSpec.fromRef("refs/something/someref"));
+ assertThat(refsToDelete(fetchRefSpecs)).isEmpty();
+ }
+
+ @Test
+ public void shouldDeleteWhenDeleteRef() {
+ List<FetchRefSpec> fetchRefSpecs = List.of(FetchRefSpec.fromRef(":refs/something/someref"));
+ assertThat(refsToDelete(fetchRefSpecs)).isEqualTo(Set.of("refs/something/someref"));
+ }
+
private void setupRequestScopeMock() {
when(scoper.scope(any()))
.thenAnswer(
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java
index a60fed6..980c509 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java
@@ -31,12 +31,14 @@
import com.google.gerrit.server.events.ProjectHeadUpdatedEvent;
import com.google.gerrit.server.events.RefUpdatedEvent;
import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.restapi.project.ProjectsCollection;
import com.googlesource.gerrit.plugins.replication.pull.ApplyObjectsCacheKey;
import com.googlesource.gerrit.plugins.replication.pull.FetchOne;
import com.googlesource.gerrit.plugins.replication.pull.Source;
import com.googlesource.gerrit.plugins.replication.pull.SourcesCollection;
import com.googlesource.gerrit.plugins.replication.pull.api.FetchAction;
import com.googlesource.gerrit.plugins.replication.pull.api.FetchJob;
+import com.googlesource.gerrit.plugins.replication.pull.api.ProjectDeletionAction;
import com.googlesource.gerrit.plugins.replication.pull.api.ProjectInitializationAction;
import com.googlesource.gerrit.plugins.replication.pull.api.PullReplicationApiRequestMetrics;
import com.googlesource.gerrit.plugins.replication.pull.api.UpdateHeadCommand;
@@ -72,6 +74,9 @@
@Mock private SourcesCollection sources;
@Mock private Source source;
@Mock private ExcludedRefsFilter refsFilter;
+ @Mock private ProjectDeletionAction projectDeletionAction;
+ @Mock private ProjectsCollection projectsCollection;
+
private Cache<ApplyObjectsCacheKey, Long> cache;
private StreamEventListener objectUnderTest;
@@ -98,7 +103,9 @@
() -> metrics,
sources,
refsFilter,
- cache);
+ cache,
+ projectDeletionAction,
+ projectsCollection);
}
@Test