Validate replication.config at Gerrit startup

When using a multi-site setup, the gerrit.replicateOnStartUp flag
on the replication.config will create a split-brain across the
cluster because assumes that the off-line master node is the
source of truth for the world, which is clearly NOT the case.

In a multi-site setup, there are always nodes up-and-running
somewhere. If a node was off, it will be very likely to be behind
and should *never* force the other nodes to comply with its
offline status.

Change-Id: I32c91dbc56fd16e35886480ffdfb258ddc42a2fa
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 e5b6190..58a11e9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
@@ -26,9 +26,11 @@
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.google.inject.spi.Message;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventFamily;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -54,6 +56,7 @@
 
   public static final String PLUGIN_NAME = "multi-site";
   public static final String MULTI_SITE_CONFIG = PLUGIN_NAME + ".config";
+  public static final String REPLICATION_CONFIG = "replication.config";
 
   static final String INSTANCE_ID_FILE = "instanceId.data";
 
@@ -79,15 +82,17 @@
   private final Supplier<KafkaSubscriber> subscriber;
   private final Supplier<Kafka> kafka;
   private final Supplier<ZookeeperConfig> zookeeperConfig;
+  private final Supplier<Collection<Message>> replicationConfigValidation;
 
   @Inject
   Configuration(SitePaths sitePaths) {
-    this(new FileBasedConfig(sitePaths.etc_dir.resolve(MULTI_SITE_CONFIG).toFile(), FS.DETECTED));
+    this(getConfigFile(sitePaths, MULTI_SITE_CONFIG), getConfigFile(sitePaths, REPLICATION_CONFIG));
   }
 
   @VisibleForTesting
-  public Configuration(final Config cfg) {
-    Supplier<Config> lazyCfg = lazyLoad(cfg);
+  public Configuration(Config multiSiteConfig, Config replicationConfig) {
+    Supplier<Config> lazyCfg = lazyLoad(multiSiteConfig);
+    replicationConfigValidation = lazyValidateReplicatioConfig(replicationConfig);
     kafka = memoize(() -> new Kafka(lazyCfg));
     publisher = memoize(() -> new KafkaPublisher(lazyCfg));
     subscriber = memoize(() -> new KafkaSubscriber(lazyCfg));
@@ -125,6 +130,14 @@
     return subscriber.get();
   }
 
+  public Collection<Message> validate() {
+    return replicationConfigValidation.get();
+  }
+
+  private static FileBasedConfig getConfigFile(SitePaths sitePaths, String configFileName) {
+    return new FileBasedConfig(sitePaths.etc_dir.resolve(configFileName).toFile(), FS.DETECTED);
+  }
+
   private Supplier<Config> lazyLoad(Config config) {
     if (config instanceof FileBasedConfig) {
       return memoize(
@@ -143,6 +156,28 @@
     return ofInstance(config);
   }
 
+  private Supplier<Collection<Message>> lazyValidateReplicatioConfig(Config replicationConfig) {
+    if (replicationConfig instanceof FileBasedConfig) {
+      FileBasedConfig fileConfig = (FileBasedConfig) replicationConfig;
+      try {
+        fileConfig.load();
+        return memoize(() -> validateReplicationConfig(replicationConfig));
+      } catch (IOException | ConfigInvalidException e) {
+        return ofInstance(Arrays.asList(new Message("Unable to load replication.config", e)));
+      }
+    }
+    return ofInstance(validateReplicationConfig(replicationConfig));
+  }
+
+  private Collection<Message> validateReplicationConfig(Config replicationConfig) {
+    if (replicationConfig.getBoolean("gerrit", "replicateOnStartup", false)) {
+      return Arrays.asList(
+          new Message(
+              "Invalid replication.config: gerrit.replicateOnStartup has to be set to 'false' for multi-site setups"));
+    }
+    return Collections.emptyList();
+  }
+
   private static int getInt(
       Supplier<Config> cfg, String section, String subSection, String name, int defaultValue) {
     try {
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 a471b04..b933635 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
@@ -18,10 +18,12 @@
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gson.Gson;
+import com.google.inject.CreationException;
 import com.google.inject.Inject;
 import com.google.inject.Provides;
 import com.google.inject.ProvisionException;
 import com.google.inject.Singleton;
+import com.google.inject.spi.Message;
 import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
 import com.googlesource.gerrit.plugins.multisite.broker.GsonProvider;
 import com.googlesource.gerrit.plugins.multisite.cache.CacheModule;
@@ -39,6 +41,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collection;
 import java.util.UUID;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -77,6 +80,11 @@
               + "and then reload the multi-site plugin.");
     }
 
+    Collection<Message> validationErrors = config.validate();
+    if (!validationErrors.isEmpty()) {
+      throw new CreationException(validationErrors);
+    }
+
     listener().to(Log4jMessageLogger.class);
     bind(MessageLogger.class).to(Log4jMessageLogger.class);
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/ConfigurationTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/ConfigurationTest.java
index 0f7b30b..4ceead9 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/ConfigurationTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/ConfigurationTest.java
@@ -43,14 +43,16 @@
   private static final int THREAD_POOL_SIZE = 1;
 
   private Config globalPluginConfig;
+  private Config replicationConfig;
 
   @Before
   public void setUp() {
     globalPluginConfig = new Config();
+    replicationConfig = new Config();
   }
 
   private Configuration getConfiguration() {
-    return new Configuration(globalPluginConfig);
+    return new Configuration(globalPluginConfig, replicationConfig);
   }
 
   @Test
@@ -203,4 +205,11 @@
 
     assertThat(property).isNull();
   }
+
+  @Test
+  public void shouldReturnValidationErrorsWhenReplicationOnStartupIsEnabled() throws Exception {
+    Config replicationConfig = new Config();
+    replicationConfig.setBoolean("gerrit", null, "replicateOnStartup", true);
+    assertThat(new Configuration(globalPluginConfig, replicationConfig).validate()).isNotEmpty();
+  }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/EventConsumerIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/EventConsumerIT.java
index 674e8de..3e8b3ca 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/EventConsumerIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/EventConsumerIT.java
@@ -50,6 +50,7 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -100,7 +101,7 @@
       this.config =
           new FileBasedConfig(
               sitePaths.etc_dir.resolve(Configuration.MULTI_SITE_CONFIG).toFile(), FS.DETECTED);
-      this.multiSiteModule = new Module(new Configuration(config), noteDb, true);
+      this.multiSiteModule = new Module(new Configuration(config, new Config()), noteDb, true);
     }
 
     @Override
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java
index 7b9ebb5..e7e7838 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java
@@ -87,7 +87,7 @@
         Configuration.ZookeeperConfig.KEY_MIGRATE,
         migrationMode);
 
-    configuration = new Configuration(splitBrainconfig);
+    configuration = new Configuration(splitBrainconfig, new Config());
     this.curator = configuration.getZookeeperConfig().buildCurator();
   }