Allow the installation of push/fetch replication filters
Define explicit configuration settings in multisite.config
for installing the push/fetch replication filter modules.
The installation as Gerrit libModule would not work because of
the need to use a child injector that has all the proper fields
and optional injections resolved at the plugin startup phase.
Adapt the multi-site plugin documentation to reflect the new
settings.
Also avoid installing the Router/Broker modules when the
only functionality needed is the replication filtering, which
is likely to be the case for Gerrit replicas.
Change-Id: Ic90adbe6dda515965aa684c0ba638a2da68b373c
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
index 69bd62f..afdb41c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
@@ -37,6 +37,7 @@
@Override
protected void configure() {
+ boolean brokerRouterNeeded = false;
Collection<Message> validationErrors = config.validate();
if (!validationErrors.isEmpty()) {
@@ -52,16 +53,20 @@
if (config.cache().synchronize()) {
install(new CacheModule());
+ brokerRouterNeeded = true;
}
if (config.event().synchronize()) {
install(new EventModule(config));
+ brokerRouterNeeded = true;
}
if (config.index().synchronize()) {
install(new IndexModule());
+ brokerRouterNeeded = true;
}
- install(new BrokerModule());
-
- install(new RouterModule());
+ if (brokerRouterNeeded) {
+ install(new BrokerModule());
+ install(new RouterModule());
+ }
}
}
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 66d91a6..c650691 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
@@ -15,11 +15,16 @@
package com.googlesource.gerrit.plugins.multisite;
import com.gerritforge.gerrit.globalrefdb.validation.ProjectDeletedSharedDbCleanup;
+import com.google.common.collect.ImmutableList;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.events.ProjectDeletedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.git.WorkQueue;
+import com.google.inject.AbstractModule;
import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.ProvisionException;
import com.google.inject.Scopes;
import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
import com.googlesource.gerrit.plugins.multisite.consumer.MultiSiteConsumerRunner;
@@ -28,13 +33,25 @@
import com.googlesource.gerrit.plugins.multisite.forwarder.broker.BrokerForwarderModule;
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"
+ };
+
private final Configuration config;
private final WorkQueue workQueue;
+ private final Injector parentInjector;
@Inject
- public PluginModule(Configuration config, WorkQueue workQueue) {
+ public PluginModule(Configuration config, WorkQueue workQueue, Injector parentInjector) {
this.config = config;
this.workQueue = workQueue;
+ this.parentInjector = parentInjector;
}
@Override
@@ -57,5 +74,39 @@
DynamicSet.bind(binder(), ProjectDeletedListener.class)
.to(ProjectDeletedSharedDbCleanup.class);
}
+
+ detectFilterModules()
+ .forEach(
+ mod -> {
+ install(mod);
+ log.atInfo().log(
+ "Replication filter module %s installed successfully",
+ mod.getClass().getSimpleName());
+ });
+ }
+
+ 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);
+ }
+ }
+
+ return filterModulesBuilder.build();
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
index f6c5b01..92a3db7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
@@ -29,14 +29,11 @@
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.replication.ReplicationExtensionPointModule;
-import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter;
public class ValidationModule extends FactoryModule {
private final Configuration cfg;
@@ -47,8 +44,6 @@
@Override
protected void configure() {
- install(new ReplicationExtensionPointModule());
-
bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
factory(LockWrapper.Factory.class);
@@ -66,8 +61,6 @@
ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF,
ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF));
bind(GitRepositoryManager.class).to(SharedRefDbGitRepositoryManager.class);
- DynamicItem.bind(binder(), ReplicationPushFilter.class)
- .to(MultisiteReplicationPushFilter.class);
if (cfg.getSharedRefDbConfiguration().getSharedRefDb().getEnforcementRules().isEmpty()) {
bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON);
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 5842100..e7681a5 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -3,8 +3,10 @@
=========================
The @PLUGIN@ plugin must be installed as a library module in the
-`$GERRIT_SITE/lib` folder of all the instances. Configuration should
-be specified in the `$site_path/etc/@PLUGIN@.config` file.
+`$GERRIT_SITE/lib` folder of all the instances and linked to
+`$GERRIT_SITE/plugins`, which enable to use it as both libModule
+and plugin.
+Configuration should be specified in the `$site_path/etc/@PLUGIN@.config` file.
## Configuration parameters
@@ -134,3 +136,50 @@
the project `foo/bar`, but no other project.
By default, all projects are matched.
+
+## Replication filters
+
+The @PLUGIN@ plugin is also responsible for filtering out replication events that may
+risk to create a split-brain situation.
+It integrates the push and pull replication filtering extension points for validating
+the refs to be replicated and dropping some of them.
+
+**Replication plugin**
+
+When using the Gerrit core replication plugin, also known as push-replication, link the
+`replication.jar` to the `$GERRIT_SITE/lib` directory and add the following libModule
+to `gerrit.config`:
+
+```
+[gerrit]
+ installModule = com.googlesource.gerrit.plugins.replication.ReplicationExtensionPointModule
+```
+
+The above configuration would be automatically detected by the @PLUGIN@ plugin which would then
+install the PushReplicationFilterModule for filtering outgoing replication refs based
+on their global-refdb status:
+
+- Outgoing replication of refs that are NOT up-to-date with the global-refdb will be
+ discarded, because they may cause split-brain on the remote replication endpoints.
+
+- All other refs will be pushed as normal to the remote replication ends.
+
+**Pull-replication plugin**
+
+When using the [pull-replication](https://gerrit.googlesource.com/plugins/pull-replication)
+plugin, link the `pull-replication.jar` to the `$GERRIT_SITE/lib` directory and add the following
+two libModules to `gerrit.config`:
+
+```
+[gerrit]
+ installModule = com.googlesource.gerrit.plugins.replication.pull.ReplicationExtensionPointModule
+```
+
+The above configuration would be automatically detected by the @PLUGIN@ plugin which would then
+install the PullReplicationFilterModule for filtering incoming fetch replication refs based
+on their global-refdb status.
+
+- Incoming replication of refs that locally are already up-to-date with the global-refdb will be
+ discarded, because they would not add anything more to the current status of the local refs.
+
+- All other refs will be fetched as normal from the replication sources.