Merge branch 'stable-3.11' into stable-3.12 * stable-3.11: Fix pull replication filtering for deleted refs Revert "Do not filter out refs removals when filtering pull-replication fetches" Change-Id: Ib85084ac3496992dc089215982f43ba4eed34e37
diff --git a/README.md b/README.md index 1e6882c..2e13f45 100644 --- a/README.md +++ b/README.md
@@ -32,21 +32,38 @@ The multi-site plugin can only be built in tree mode, by cloning Gerrit and the multi-site plugin code, and checking them out on the desired branch. -Example of cloning Gerrit and multi-site for a stable-2.16 build: +To build the multi-site plugin in addition to the gerrit core plugins also the +following plugins need to be present in the plugins directory: + +- pull-replication +- healthcheck +- events-broker +- global-refdb + +Example of cloning Gerrit, the multi-site plugin and the plugins it depends on +for a stable-3.11 build: ``` -git clone -b stable-2.16 https://gerrit.googlesource.com/gerrit -git clone -b stable-2.16 https://gerrit.googlesource.com/plugins/multi-site +git clone --recurse-submodules -b stable-3.11 https://gerrit.googlesource.com/gerrit +git clone -b stable-3.11 https://gerrit.googlesource.com/plugins/multi-site +git clone -b stable-3.11 https://gerrit.googlesource.com/modules/events-broker +git clone -b stable-3.11 https://gerrit.googlesource.com/modules/global-refdb +git clone -b stable-3.11 https://gerrit.googlesource.com/plugins/healthcheck +git clone -b stable-3.11 https://gerrit.googlesource.com/plugins/pull-replication cd gerrit/plugins ln -s ../../multi-site . +ln -s ../../events-broker . +ln -s ../../global-refdb . +ln -s ../../healthcheck . +ln -s ../../pull-replication . ``` Example of building the multi-site plugin: ``` cd gerrit -bazel build plugins/multi-site +bazelisk build plugins/multi-site ``` The multi-site.jar plugin is generated to `bazel-bin/plugins/multi-site/multi-site.jar`. @@ -55,13 +72,13 @@ ``` cd gerrit -bazel test plugins/multi-site:multi_site_tests +bazelisk test plugins/multi-site/... ``` **NOTE**: The multi-site tests include also the use of Docker containers for instantiating and using a Kafka/Zookeeper broker. Make sure you have a Docker daemon running (/var/run/docker.sock accessible) or a DOCKER_HOST pointing to -a Docker server. +a Docker server. In addition docker-compose needs to be installed. ## Pre-requisites
diff --git a/setup_local_env/setup.sh b/setup_local_env/setup.sh index 3e79f14..d7ab200 100755 --- a/setup_local_env/setup.sh +++ b/setup_local_env/setup.sh
@@ -16,7 +16,7 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -GERRIT_BRANCH=stable-3.11 +GERRIT_BRANCH=stable-3.12 GERRIT_CI=https://gerrit-ci.gerritforge.com/view/Plugins-$GERRIT_BRANCH/job LAST_BUILD=lastSuccessfulBuild/artifact/bazel-bin/plugins
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java index 4415aea..1be09aa 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
@@ -58,6 +58,8 @@ private static final String REPLICATION_LAG_REFRESH_INTERVAL = "replicationLagRefreshInterval"; private static final String REPLICATION_LAG_ENABLED = "replicationLagEnabled"; private static final Duration REPLICATION_LAG_REFRESH_INTERVAL_DEFAULT = Duration.ofSeconds(60); + private static final String PUSH_REPLICATION_FILTER_ENABLED = "pushReplicationFilterEnabled"; + private static final String PULL_REPLICATION_FILTER_ENABLED = "pullReplicationFilterEnabled"; private static final String LOCAL_REF_LOCK_TIMEOUT = "localRefLockTimeout"; private static final Duration LOCAL_REF_LOCK_TIMEOUT_DEFAULT = Duration.ofSeconds(30); @@ -81,6 +83,8 @@ private final Config multiSiteConfig; private final Supplier<Duration> replicationLagRefreshInterval; private final Supplier<Boolean> replicationLagEnabled; + private final Supplier<Boolean> pushReplicationFilterEnabled; + private final Supplier<Boolean> pullReplicationFilterEnabled; private final Supplier<Long> localRefLockTimeoutMsec; @Inject @@ -121,6 +125,19 @@ lazyMultiSiteCfg .get() .getBoolean(REF_DATABASE, null, REPLICATION_LAG_ENABLED, true)); + + pushReplicationFilterEnabled = + memoize( + () -> + lazyMultiSiteCfg + .get() + .getBoolean(REF_DATABASE, null, PUSH_REPLICATION_FILTER_ENABLED, true)); + pullReplicationFilterEnabled = + memoize( + () -> + lazyMultiSiteCfg + .get() + .getBoolean(REF_DATABASE, null, PULL_REPLICATION_FILTER_ENABLED, true)); localRefLockTimeoutMsec = memoize( () -> @@ -173,6 +190,14 @@ return replicationLagEnabled.get(); } + public boolean pushReplicationFilterEnabled() { + return pushReplicationFilterEnabled.get(); + } + + public boolean pullRepllicationFilterEnabled() { + return pullReplicationFilterEnabled.get(); + } + public long localRefLockTimeoutMsec() { return localRefLockTimeoutMsec.get(); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java index db06c9f..3ced8c6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
@@ -35,14 +35,10 @@ public class PluginModule extends LifecycleModule { private static final FluentLogger log = FluentLogger.forEnclosingClass(); - private static final String[] FILTER_MODULES_CLASS_NAMES = - new String[] { - /* Class names are defined as String for avoiding this class failing to load - * if either replication or pull-replication plugins are missing. - */ - "com.googlesource.gerrit.plugins.multisite.validation.PullReplicationFilterModule", - "com.googlesource.gerrit.plugins.multisite.validation.PushReplicationFilterModule" - }; + public static final String PULL_REPLICATION_FILTER_MODULE = + "com.googlesource.gerrit.plugins.multisite.validation.PullReplicationFilterModule"; + public static final String PUSH_REPLICATION_FILTER_MODULE = + "com.googlesource.gerrit.plugins.multisite.validation.PushReplicationFilterModule"; private final Configuration config; private final WorkQueue workQueue; @@ -89,25 +85,33 @@ private Iterable<AbstractModule> detectFilterModules() { ImmutableList.Builder<AbstractModule> filterModulesBuilder = ImmutableList.builder(); - for (String filterClassName : FILTER_MODULES_CLASS_NAMES) { - try { - @SuppressWarnings("unchecked") - Class<AbstractModule> filterClass = (Class<AbstractModule>) Class.forName(filterClassName); - - AbstractModule filterModule = parentInjector.getInstance(filterClass); - // Check if the filterModule would be valid for creating a child Guice Injector - parentInjector.createChildInjector(filterModule); - - filterModulesBuilder.add(filterModule); - } catch (NoClassDefFoundError | ClassNotFoundException e) { - log.atFine().withCause(e).log( - "Not loading %s because of missing the associated replication plugin", filterClassName); - } catch (Exception e) { - throw new ProvisionException( - "Unable to instantiate replication filter " + filterClassName, e); - } + if (config.pushReplicationFilterEnabled()) { + bindReplicationFilterClass(PUSH_REPLICATION_FILTER_MODULE, filterModulesBuilder); + } + if (config.pullRepllicationFilterEnabled()) { + bindReplicationFilterClass(PULL_REPLICATION_FILTER_MODULE, filterModulesBuilder); } return filterModulesBuilder.build(); } + + private void bindReplicationFilterClass( + String filterClassName, ImmutableList.Builder<AbstractModule> filterModulesBuilder) { + try { + @SuppressWarnings("unchecked") + Class<AbstractModule> filterClass = (Class<AbstractModule>) Class.forName(filterClassName); + + AbstractModule filterModule = parentInjector.getInstance(filterClass); + // Check if the filterModule would be valid for creating a child Guice Injector + parentInjector.createChildInjector(filterModule); + + filterModulesBuilder.add(filterModule); + } catch (NoClassDefFoundError | ClassNotFoundException e) { + log.atWarning().withCause(e).log( + "Not loading %s because of missing the associated replication plugin", filterClassName); + } catch (Exception e) { + throw new ProvisionException( + "Unable to instantiate replication filter " + filterClassName, e); + } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcher.java similarity index 87% rename from src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java rename to src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcher.java index e2e3954..aeee827 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcher.java
@@ -22,23 +22,24 @@ import com.google.gerrit.server.util.OneOffRequestContext; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.googlesource.gerrit.plugins.multisite.forwarder.router.StreamEventRouter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Dispatch event to the {@link EventDispatcher}. This class is meant to be used on the receiving - * side of the {@link StreamEventForwarder} since it will prevent dispatched event to be forwarded + * side of the {@link StreamEventRouter} since it will prevent dispatched event to be forwarded * again causing an infinite forwarding loop between the 2 nodes. */ @Singleton -public class ForwardedEventHandler { - private static final Logger log = LoggerFactory.getLogger(ForwardedEventHandler.class); +public class ForwardedEventDispatcher { + private static final Logger log = LoggerFactory.getLogger(ForwardedEventDispatcher.class); private final DynamicItem<EventDispatcher> dispatcher; private final OneOffRequestContext oneOffCtx; @Inject - public ForwardedEventHandler( + public ForwardedEventDispatcher( DynamicItem<EventDispatcher> dispatcher, OneOffRequestContext oneOffCtx) { this.dispatcher = dispatcher; this.oneOffCtx = oneOffCtx;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java index 15f7c13..8a52eb5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java
@@ -16,6 +16,7 @@ import com.google.common.base.Splitter; import com.google.gerrit.entities.Change; +import com.google.gerrit.entities.Project; import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.util.ManualRequestContext; @@ -103,6 +104,28 @@ } } + /** + * Deletes all change index entries associated with the given project. + * + * <p>This method is invoked when a project-wide deletion event is received from a peer in a + * multi-site setup. It removes all changes for the specified project from the local index. + * + * <p>The method temporarily marks the execution context as a forwarded event to prevent the + * resulting index updates from being re-forwarded to other peers, which would otherwise create + * event loops. + * + * @param projectName the name of the project whose changes should be removed from the index + */ + public void deleteAllForProject(String projectName) { + try { + Context.setForwardedEvent(true); + log.debug("Deleting all change indexes for project {}", projectName); + indexer.deleteAllForProject(Project.nameKey(projectName)); + } finally { + Context.unsetForwardedEvent(); + } + } + @Override protected void reindex(String id) { try (ManualRequestContext ctx = oneOffCtx.open()) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java index 64fbdfb..af4c0bc 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java
@@ -22,12 +22,37 @@ public class ChangeIndexEvent extends IndexEvent { static final String TYPE = "change-index"; + private static final int ALL_CHANGES_FOR_PROJECT = 0; public String projectName; public int changeId; public String targetSha; public boolean deleted; + /** + * Creates a {@link ChangeIndexEvent} that represents the deletion of all changes belonging to the + * given project. + * + * @param projectName the name of the project + * @param instanceId the originating instance identifier + * @return an event marking all project changes as deleted + */ + public static ChangeIndexEvent allChangesDeletedForProject( + String projectName, String instanceId) { + return new ChangeIndexEvent( + projectName, ALL_CHANGES_FOR_PROJECT, /* deleted */ true, instanceId); + } + + /** + * Checks whether the given event represents the deletion of all changes for a project. + * + * @param event the event to check + * @return {@code true} if the event signals a project-wide deletion, {@code false} otherwise + */ + public static boolean isAllChangesDeletedForProject(ChangeIndexEvent event) { + return event.deleted && event.changeId == ALL_CHANGES_FOR_PROJECT; + } + public ChangeIndexEvent(String projectName, int changeId, boolean deleted, String instanceId) { super(TYPE, instanceId); this.projectName = projectName;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java index a647bce..a23ca33 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java
@@ -71,11 +71,16 @@ public void route(IndexEvent sourceEvent) throws IOException { if (sourceEvent instanceof ChangeIndexEvent) { ChangeIndexEvent changeIndexEvent = (ChangeIndexEvent) sourceEvent; - ForwardedIndexingHandler.Operation operation = changeIndexEvent.deleted ? DELETE : INDEX; - indexChangeHandler.index( - changeIndexEvent.projectName + "~" + changeIndexEvent.changeId, - operation, - Optional.of(changeIndexEvent)); + + if (ChangeIndexEvent.isAllChangesDeletedForProject(changeIndexEvent)) { + indexChangeHandler.deleteAllForProject(changeIndexEvent.projectName); + } else { + ForwardedIndexingHandler.Operation operation = changeIndexEvent.deleted ? DELETE : INDEX; + indexChangeHandler.index( + changeIndexEvent.projectName + "~" + changeIndexEvent.changeId, + operation, + Optional.of(changeIndexEvent)); + } } else if (sourceEvent instanceof AccountIndexEvent) { AccountIndexEvent accountIndexEvent = (AccountIndexEvent) sourceEvent; indexAccountHandler.indexAsync(Account.id(accountIndexEvent.accountId), INDEX);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java index 95a3e66..c88f5cb 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java
@@ -17,18 +17,18 @@ import com.google.gerrit.server.events.Event; import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.inject.Inject; -import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventHandler; +import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventDispatcher; import com.googlesource.gerrit.plugins.replication.events.RefReplicationDoneEvent; import java.io.IOException; public class StreamEventRouter implements ForwardedEventRouter<Event> { - private final ForwardedEventHandler streamEventHandler; + private final ForwardedEventDispatcher forwardedEventDispatcher; private final IndexEventRouter indexEventRouter; @Inject public StreamEventRouter( - ForwardedEventHandler streamEventHandler, IndexEventRouter indexEventRouter) { - this.streamEventHandler = streamEventHandler; + ForwardedEventDispatcher forwardedEventDispatcher, IndexEventRouter indexEventRouter) { + this.forwardedEventDispatcher = forwardedEventDispatcher; this.indexEventRouter = indexEventRouter; } @@ -44,6 +44,6 @@ indexEventRouter.onRefReplicated((RefReplicationDoneEvent) sourceEvent); } - streamEventHandler.dispatch(sourceEvent); + forwardedEventDispatcher.dispatch(sourceEvent); } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/FetchRefReplicatedEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/FetchRefReplicatedEventHandler.java index 83076bf..69cdb06 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/FetchRefReplicatedEventHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/FetchRefReplicatedEventHandler.java
@@ -18,6 +18,7 @@ import com.google.gerrit.entities.Change; import com.google.gerrit.entities.Project; import com.google.gerrit.entities.RefNames; +import com.google.gerrit.exceptions.StorageException; import com.google.gerrit.server.config.GerritInstanceId; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.EventListener; @@ -28,6 +29,7 @@ import com.googlesource.gerrit.plugins.replication.pull.FetchRefReplicatedEvent; import com.googlesource.gerrit.plugins.replication.pull.ReplicationState; import java.io.IOException; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; @@ -63,7 +65,8 @@ fetchRefReplicatedEvent.ref.startsWith(":") ? fetchRefReplicatedEvent.ref.substring(1) : fetchRefReplicatedEvent.ref; - if (!RefNames.isNoteDbMetaRef(refName) + boolean isMetaRef = RefNames.isNoteDbMetaRef(refName); + if (!RefNames.isRefsChanges(refName) || !fetchRefReplicatedEvent .getStatus() .equals(ReplicationState.RefFetchResult.SUCCEEDED.toString())) { @@ -71,6 +74,9 @@ } Project.NameKey projectNameKey = fetchRefReplicatedEvent.getProjectNameKey(); + logger.atFine().log( + "Indexing ref '%s' for project %s", + fetchRefReplicatedEvent.getRefName(), projectNameKey.get()); Change.Id changeId = Change.Id.fromRef(refName); try (Repository repo = gitRepositoryManager.openRepository(projectNameKey)) { @@ -93,12 +99,32 @@ // so that multi-site can use it for computing the virtual change-id and assuring the // delete // of the imported changes as well. - changeIndexer.delete(changeId); + if (isMetaRef) { + catchStorageExceptionForMissingUnknown( + () -> changeIndexer.delete(changeId), + "Skipping deleting from index of " + + projectNameKey + + "~" + + changeId.get() + + " because its patch-sets aren't replicated yet"); + } else { + changeIndexer.delete(changeId); + } } else { logger.atInfo().log( "Indexing change '%s' following update of '%s' on project %s", changeId, refName, projectNameKey.get()); - changeIndexer.index(projectNameKey, changeId); + if (isMetaRef) { + catchStorageExceptionForMissingUnknown( + () -> changeIndexer.index(projectNameKey, changeId), + "Skipping indexing of " + + projectNameKey + + "~" + + changeId.get() + + " because its patch-sets aren't replicated yet"); + } else { + changeIndexer.index(projectNameKey, changeId); + } } } else { logger.atWarning().log( @@ -114,6 +140,23 @@ } } + private void catchStorageExceptionForMissingUnknown(Runnable lambda, String infoMessage) + throws RuntimeException { + try { + lambda.run(); + } catch (StorageException se) { + Throwable cause = se.getCause(); + while (cause != null) { + if (cause instanceof MissingObjectException) { + logger.atInfo().log("%s", infoMessage); + return; + } + cause = cause.getCause(); + } + throw se; + } + } + private boolean isLocalEvent(Event event) { return instanceId.equals(event.instanceId); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java index 9aee56e..83f610d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
@@ -85,6 +85,11 @@ } @Override + public void onAllChangesDeletedForProject(String projectName) { + currCtx.onlyWithContext((ctx) -> executeAllChangesDeletedForProject(projectName)); + } + + @Override public void onChangeDeleted(int id) { executeDeleteChangeTask(id); } @@ -111,6 +116,17 @@ } } + private void executeAllChangesDeletedForProject(String projectName) { + if (!Context.isForwardedEvent()) { + IndexChangeTask task = + new IndexChangeTask( + ChangeIndexEvent.allChangesDeletedForProject(projectName, instanceId)); + if (queuedTasks.add(task)) { + executor.execute(task); + } + } + } + private void executeIndexChangeTask(String projectName, int id) { if (!Context.isForwardedEvent()) { ChangeChecker checker = changeChecker.create(projectName + "~" + id);
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index 55e5041..ed72405 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -95,6 +95,16 @@ : Enable the use of a shared ref-database Defaults: true +```ref-database.pushReplicationFilterClassEnabled``` +: Enable the filtering of push replication events checking their + up-to-date status with the global-refdb. + Defaults: true + +```ref-database.pullReplicationFilterClassEnabled``` +: Enable the filtering of pull replication events checking their + up-to-date status with the global-refdb. + Defaults: true + ```ref-database.localRefLockTimeout``` : Timeout waiting for a local ref to become available to accept updates or for starting a replication task.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java index 4ffdbbc..8239cf2 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java
@@ -20,7 +20,7 @@ import com.google.gerrit.entities.Account; import com.google.gerrit.server.config.AllUsersName; -import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventHandler; +import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventDispatcher; import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexAccountHandler; import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexChangeHandler; import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexGroupHandler; @@ -50,7 +50,7 @@ @Mock private ForwardedIndexChangeHandler indexChangeHandler; @Mock private ForwardedIndexGroupHandler indexGroupHandler; @Mock private ForwardedIndexProjectHandler indexProjectHandler; - @Mock private ForwardedEventHandler forwardedEventHandler; + @Mock private ForwardedEventDispatcher forwardedEventDispatcher; private AllUsersName allUsersName = new AllUsersName("All-Users"); @Before @@ -79,7 +79,7 @@ @Test public void streamEventRouterShouldTriggerAccountIndexFlush() throws Exception { - StreamEventRouter streamEventRouter = new StreamEventRouter(forwardedEventHandler, router); + StreamEventRouter streamEventRouter = new StreamEventRouter(forwardedEventDispatcher, router); final AccountIndexEvent event = new AccountIndexEvent(1, INSTANCE_ID); router.route(event); @@ -154,4 +154,16 @@ verifyNoInteractions( indexAccountHandler, indexChangeHandler, indexGroupHandler, indexProjectHandler); } + + @Test + public void routerShouldSendEventsToTheAppropriateHandler_allChangesDeletedForProject() + throws Exception { + ChangeIndexEvent event = + ChangeIndexEvent.allChangesDeletedForProject("projectName", INSTANCE_ID); + router.route(event); + + verify(indexChangeHandler).deleteAllForProject(event.projectName); + + verifyNoInteractions(indexAccountHandler, indexGroupHandler, indexProjectHandler); + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java index ac90436..8d02c20 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java
@@ -21,7 +21,7 @@ import com.google.gerrit.entities.Change; import com.google.gerrit.server.events.CommentAddedEvent; import com.google.gerrit.server.util.time.TimeUtil; -import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventHandler; +import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventDispatcher; import com.googlesource.gerrit.plugins.multisite.forwarder.router.IndexEventRouter; import com.googlesource.gerrit.plugins.multisite.forwarder.router.StreamEventRouter; import org.junit.Before; @@ -34,19 +34,19 @@ public class StreamEventRouterTest { private StreamEventRouter router; - @Mock private ForwardedEventHandler streamEventHandler; + @Mock private ForwardedEventDispatcher forwardedEventDispatcher; @Mock private IndexEventRouter indexEventRouter; @Before public void setUp() { - router = new StreamEventRouter(streamEventHandler, indexEventRouter); + router = new StreamEventRouter(forwardedEventDispatcher, indexEventRouter); } @Test public void routerShouldSendEventsToTheAppropriateHandler_StreamEvent() throws Exception { final CommentAddedEvent event = new CommentAddedEvent(aChange()); router.route(event); - verify(streamEventHandler).dispatch(event); + verify(forwardedEventDispatcher).dispatch(event); } private Change aChange() {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcherTest.java similarity index 87% rename from src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java rename to src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcherTest.java index 73ef353..bf70f62 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventDispatcherTest.java
@@ -31,24 +31,24 @@ import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) -public class ForwardedEventHandlerTest { +public class ForwardedEventDispatcherTest { @Rule public ExpectedException exception = ExpectedException.none(); @Mock private DynamicItem<EventDispatcher> dispatcherMockItem; @Mock private EventDispatcher dispatcherMock; @Mock OneOffRequestContext oneOffCtxMock; - private ForwardedEventHandler handler; + private ForwardedEventDispatcher forwardedEventDispatcher; @Before public void setUp() throws Exception { when(dispatcherMockItem.get()).thenReturn(dispatcherMock); - handler = new ForwardedEventHandler(dispatcherMockItem, oneOffCtxMock); + forwardedEventDispatcher = new ForwardedEventDispatcher(dispatcherMockItem, oneOffCtxMock); } @Test public void testSuccessfulDispatching() throws Exception { Event event = new ProjectCreatedEvent(); - handler.dispatch(event); + forwardedEventDispatcher.dispatch(event); verify(dispatcherMock).postEvent(event); } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java index 7133a0d..984e39f 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java
@@ -203,6 +203,13 @@ verify(indexerMock, times(1)).index(any(ChangeNotes.class)); } + @Test + public void shouldDeleteAllForProject() { + handler.deleteAllForProject(TEST_PROJECT); + + verify(indexerMock, times(1)).deleteAllForProject(Project.nameKey(TEST_PROJECT)); + } + private void setupChangeAccessRelatedMocks(boolean changeExist, boolean changeUpToDate) throws Exception { setupChangeAccessRelatedMocks(
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/FetchRefReplicatedEventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/FetchRefReplicatedEventHandlerTest.java index ed2f958..f7ef474 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/index/FetchRefReplicatedEventHandlerTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/index/FetchRefReplicatedEventHandlerTest.java
@@ -14,9 +14,12 @@ package com.googlesource.gerrit.plugins.multisite.index; +import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.testing.GerritJUnit.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -24,12 +27,15 @@ import com.google.gerrit.entities.Change; import com.google.gerrit.entities.Project; +import com.google.gerrit.exceptions.StorageException; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.index.change.ChangeIndexer; import com.googlesource.gerrit.plugins.replication.pull.Context; import com.googlesource.gerrit.plugins.replication.pull.FetchRefReplicatedEvent; import com.googlesource.gerrit.plugins.replication.pull.ReplicationState; import com.googlesource.gerrit.plugins.replication.pull.ReplicationState.RefFetchResult; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.URIish; @@ -98,6 +104,65 @@ } @Test + public void onEventShouldIgnoreMissingObjectWhenIndexExistingChangeMeta() { + Project.NameKey projectNameKey = Project.nameKey("testProject"); + String ref = "refs/changes/41/41/meta"; + Change.Id changeId = Change.Id.fromRef(ref); + try { + Context.setLocalEvent(true); + StorageException storageExceptionForMissingObject = + new StorageException( + "Test storage exception", + new MissingObjectException(ObjectId.zeroId(), "Missing unknown")); + doThrow(storageExceptionForMissingObject) + .when(changeIndexerMock) + .index(eq(projectNameKey), eq(changeId)); + fetchRefReplicatedEventHandler.onEvent( + newFetchRefReplicatedEvent( + projectNameKey, + ref, + ReplicationState.RefFetchResult.SUCCEEDED, + LOCAL_INSTANCE_ID, + RefUpdate.Result.FAST_FORWARD)); + verify(changeIndexerMock, times(1)).index(eq(projectNameKey), eq(changeId)); + } finally { + Context.unsetLocalEvent(); + } + } + + @Test + public void onEventShouldThrowStorageExceptionWhenIndexPatchSetsWithMissingRefs() { + Project.NameKey projectNameKey = Project.nameKey("testProject"); + String ref = "refs/changes/41/41/1"; + Change.Id changeId = Change.Id.fromRef(ref); + try { + Context.setLocalEvent(true); + StorageException storageExceptionForMissingObject = + new StorageException( + "Test storage exception", + new MissingObjectException(ObjectId.zeroId(), "Missing unknown")); + doThrow(storageExceptionForMissingObject) + .when(changeIndexerMock) + .index(eq(projectNameKey), eq(changeId)); + StorageException indexException = + assertThrows( + StorageException.class, + () -> + fetchRefReplicatedEventHandler.onEvent( + newFetchRefReplicatedEvent( + projectNameKey, + ref, + RefFetchResult.SUCCEEDED, + LOCAL_INSTANCE_ID, + RefUpdate.Result.FAST_FORWARD))); + assertThat(indexException).isEqualTo(storageExceptionForMissingObject); + verify(changeIndexerMock, times(1)).index(eq(projectNameKey), eq(changeId)); + } finally { + Context.unsetLocalEvent(); + } + } + + @Test public void onEventShouldNotIndexIfNotLocalEvent() { Project.NameKey projectNameKey = Project.nameKey("testProject"); String ref = "refs/changes/41/41/meta"; @@ -113,7 +178,7 @@ } @Test - public void onEventShouldIndexOnlyMetaRef() { + public void onEventShouldIndexOnPatchSetsRef() { Project.NameKey projectNameKey = Project.nameKey("testProject"); String ref = "refs/changes/41/41/1"; Change.Id changeId = Change.Id.fromRef(ref); @@ -124,7 +189,7 @@ ReplicationState.RefFetchResult.SUCCEEDED, LOCAL_INSTANCE_ID, RefUpdate.Result.FAST_FORWARD)); - verify(changeIndexerMock, never()).index(eq(projectNameKey), eq(changeId)); + verify(changeIndexerMock).index(eq(projectNameKey), eq(changeId)); } @Test