Merge branch 'stable-3.2' into stable-3.3 * stable-3.2: Upgrade bazlets to latest stable-3.2 to build with 3.2.5.1 API Fix plugin's documentation after 8fb718b Upgrade bazlets to latest stable-3.1 to build with 3.1.10 API Change-Id: I0f3d2bca8a2639d488be390f94c73956e02bc667
diff --git a/BUILD b/BUILD index 3b896ff..5975b31 100644 --- a/BUILD +++ b/BUILD
@@ -19,7 +19,10 @@ "Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/high-availability", ], resources = glob(["src/main/resources/**/*"]), - deps = ["@jgroups//jar"], + deps = [ + "@jgroups//jar", + "@global-refdb//jar", + ], ) junit_tests( @@ -42,6 +45,7 @@ visibility = ["//visibility:public"], exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [ ":high-availability__plugin", + "@global-refdb//jar", "@wiremock//jar", ], )
diff --git a/WORKSPACE b/WORKSPACE index 83e0599..cdb1696 100644 --- a/WORKSPACE +++ b/WORKSPACE
@@ -12,7 +12,7 @@ "gerrit_api", ) -gerrit_api() +gerrit_api(version = "3.3.0-SNAPSHOT") load("//:external_plugin_deps.bzl", "external_plugin_deps")
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl index 0f8b43b..cb3f043 100644 --- a/external_plugin_deps.bzl +++ b/external_plugin_deps.bzl
@@ -12,3 +12,9 @@ artifact = "org.jgroups:jgroups:3.6.15.Final", sha1 = "755afcfc6c8a8ea1e15ef0073417c0b6e8c6d6e4", ) + + maven_jar( + name = "global-refdb", + artifact = "com.gerritforge:global-refdb:3.3.0-rc1", + sha1 = "1b005b31c27a30ff10de97f903fa2834051bcadf", + )
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java index 3bd58a3..9af312d 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -17,18 +17,19 @@ import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.flogger.FluentLogger; import com.google.gerrit.common.Nullable; -import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.server.config.ConfigUtil; -import com.google.gerrit.server.config.PluginConfigFactory; import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; import com.google.inject.Singleton; +import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; @@ -39,7 +40,10 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; @Singleton public class Configuration { @@ -47,6 +51,7 @@ public static final int DEFAULT_NUM_STRIPED_LOCKS = 10; public static final int DEFAULT_TIMEOUT_MS = 5000; + public static final String PLUGIN_CONFIG_FILE = "high-availability.config"; // common parameter to peerInfo section static final String PEER_INFO_SECTION = "peerInfo"; @@ -70,6 +75,7 @@ private PeerInfoStatic peerInfoStatic; private PeerInfoJGroups peerInfoJGroups; private HealthCheck healthCheck; + private final SharedRefDbConfiguration sharedRefDb; public enum PeerInfoStrategy { JGROUPS, @@ -77,9 +83,12 @@ } @Inject - Configuration( - PluginConfigFactory pluginConfigFactory, @PluginName String pluginName, SitePaths site) { - Config cfg = pluginConfigFactory.getGlobalPluginConfig(pluginName); + Configuration(SitePaths sitePaths) { + this(getConfigFile(sitePaths, PLUGIN_CONFIG_FILE), sitePaths); + } + + @VisibleForTesting + Configuration(Config cfg, SitePaths site) { main = new Main(site, cfg); autoReindex = new AutoReindex(cfg); peerInfo = new PeerInfo(cfg); @@ -100,6 +109,20 @@ index = new Index(cfg); websession = new Websession(cfg); healthCheck = new HealthCheck(cfg); + sharedRefDb = new SharedRefDbConfiguration(cfg); + } + + private static FileBasedConfig getConfigFile(SitePaths sitePaths, String configFileName) { + FileBasedConfig cfg = + new FileBasedConfig(sitePaths.etc_dir.resolve(configFileName).toFile(), FS.DETECTED); + String fileConfigFileName = cfg.getFile().getPath(); + try { + log.atInfo().log("Loading configuration from {}", fileConfigFileName); + cfg.load(); + } catch (IOException | ConfigInvalidException e) { + log.atSevere().withCause(e).log("Unable to load configuration from " + fileConfigFileName); + } + return cfg; } public Main main() { @@ -150,6 +173,10 @@ return healthCheck; } + public SharedRefDbConfiguration sharedRefDb() { + return sharedRefDb; + } + private static int getInt(Config cfg, String section, String name, int defaultValue) { try { return cfg.getInt(section, name, defaultValue);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java index 744fac9..ddc294b 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
@@ -21,7 +21,7 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.RestForwarderModule; import com.ericsson.gerrit.plugins.highavailability.index.IndexModule; import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfoModule; -import com.google.inject.AbstractModule; +import com.google.gerrit.lifecycle.LifecycleModule; import com.google.inject.Inject; import com.google.inject.Provides; import com.google.inject.Singleton; @@ -29,7 +29,7 @@ import java.nio.file.Files; import java.nio.file.Path; -class Module extends AbstractModule { +class Module extends LifecycleModule { private final Configuration config; @Inject @@ -55,6 +55,10 @@ install(new AutoReindexModule()); } install(new PeerInfoModule(config.peerInfo().strategy())); + + if (config.sharedRefDb().getSharedRefDb().isEnabled()) { + listener().to(PluginStartup.class); + } } @Provides
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/PluginStartup.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/PluginStartup.java new file mode 100644 index 0000000..5c19eec --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/PluginStartup.java
@@ -0,0 +1,45 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.ericsson.gerrit.plugins.highavailability; + +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper; +import com.google.gerrit.extensions.events.LifecycleListener; +import com.google.inject.Inject; +import com.google.inject.Injector; + +/** + * Purpose of this class is to copy SharedRefDatabaseWrapper instance from DB injector to plugin + * injector. This allows to bind different global ref-db implementations. + */ +public class PluginStartup implements LifecycleListener { + private SharedRefDatabaseWrapper sharedRefDb; + private Injector injector; + + @Inject + public PluginStartup(SharedRefDatabaseWrapper sharedRefDb, Injector injector) { + this.sharedRefDb = sharedRefDb; + this.injector = injector; + } + + @Override + public void start() { + injector.injectMembers(sharedRefDb); + } + + @Override + public void stop() { + // Nothing to do + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java new file mode 100644 index 0000000..74db7d2 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ValidationModule.java
@@ -0,0 +1,70 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.ericsson.gerrit.plugins.highavailability; + +import com.gerritforge.gerrit.globalrefdb.validation.BatchRefUpdateValidator; +import com.gerritforge.gerrit.globalrefdb.validation.LockWrapper; +import com.gerritforge.gerrit.globalrefdb.validation.Log4jSharedRefLogger; +import com.gerritforge.gerrit.globalrefdb.validation.RefUpdateValidator; +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper; +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbBatchRefUpdate; +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration; +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbGitRepositoryManager; +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefDatabase; +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefUpdate; +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRepository; +import com.gerritforge.gerrit.globalrefdb.validation.SharedRefLogger; +import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.CustomSharedRefEnforcementByProject; +import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement; +import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement; +import com.google.gerrit.extensions.config.FactoryModule; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.inject.Inject; +import com.google.inject.Scopes; + +public class ValidationModule extends FactoryModule { + final Configuration configuration; + + @Inject + public ValidationModule(Configuration configuration) { + this.configuration = configuration; + } + + @Override + protected void configure() { + factory(SharedRefDbRepository.Factory.class); + factory(SharedRefDbRefDatabase.Factory.class); + factory(SharedRefDbRefUpdate.Factory.class); + factory(SharedRefDbBatchRefUpdate.Factory.class); + factory(RefUpdateValidator.Factory.class); + factory(BatchRefUpdateValidator.Factory.class); + + bind(SharedRefDbConfiguration.class).toInstance(configuration.sharedRefDb()); + + bind(SharedRefDatabaseWrapper.class).in(Scopes.SINGLETON); + bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class); + factory(LockWrapper.Factory.class); + + bind(GitRepositoryManager.class).to(SharedRefDbGitRepositoryManager.class); + + if (configuration.sharedRefDb().getSharedRefDb().getEnforcementRules().isEmpty()) { + bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON); + } else { + bind(SharedRefEnforcement.class) + .to(CustomSharedRefEnforcementByProject.class) + .in(Scopes.SINGLETON); + } + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java index e37234c..1d31ebc 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
@@ -15,7 +15,7 @@ package com.ericsson.gerrit.plugins.highavailability.autoreindex; import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet; -import com.google.gerrit.common.data.GroupReference; +import com.google.gerrit.entities.GroupReference; import com.google.gerrit.server.group.db.Groups; import com.google.gerrit.server.util.OneOffRequestContext; import com.google.inject.Inject;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java index fe04ee9..6587acf 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
@@ -17,7 +17,7 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; import com.google.common.flogger.FluentLogger; import com.google.gerrit.entities.Change; -import com.google.gerrit.entities.Comment; +import com.google.gerrit.entities.HumanComment; import com.google.gerrit.server.CommentsUtil; import com.google.gerrit.server.change.ChangeFinder; import com.google.gerrit.server.git.GitRepositoryManager; @@ -144,7 +144,7 @@ private long getTsFromChangeAndDraftComments(ChangeNotes notes) { Change change = notes.getChange(); Timestamp changeTs = change.getLastUpdatedOn(); - for (Comment comment : commentsUtil.draftByChange(changeNotes.get())) { + for (HumanComment comment : commentsUtil.draftByChange(changeNotes.get())) { Timestamp commentTs = comment.writtenOn; changeTs = commentTs.after(changeTs) ? commentTs : changeTs; }
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md index fd0b56e..851f584 100644 --- a/src/main/resources/Documentation/about.md +++ b/src/main/resources/Documentation/about.md
@@ -7,10 +7,10 @@ * sharing the git repositories using a shared file system (e.g. NFS) * behind a load balancer (e.g. HAProxy) -Currently, the mode supported is one active instance and multiple backup -(passive) instances but eventually the plan is to support `n` active instances. -In the active/passive mode, the active instance is handling all traffic while -the passives are kept updated to be always ready to take over. +Default mode is one active instance and multiple backup (passive) instances +but `n` active instances can be configured. In the active/passive mode, the active +instance is handling all traffic while the passives are kept updated to be always +ready to take over. Even if git repositories are shared by the instances, there are a few areas of concern in order to be able to switch traffic between instances in a @@ -46,7 +46,7 @@ The built-in Gerrit H2 based web session cache is replaced with a file based implementation that is shared amongst the instances. -## Setup +## Active/passive setup Prerequisites: @@ -103,3 +103,72 @@ For further information and supported options, refer to [config](config.md) documentation. + +## Active/active setup + +Prerequisites: + +* Git repositories must be located on a shared file system +* A directory on a shared file system must be available for @PLUGIN@ to use +* An implementation of global-refdb (e.g. Zookeeper) must be accessible from all the active +instances + +For the instances: + +* Configure gerrit.basePath in gerrit.config to the shared repositories location +* Configure gerrit.serverId in gerrit.config based on [config](config.md)'s introduction +* Install and configure this @PLUGIN@ plugin [further](config.md) or based on example +configuration +* Install @PLUGIN@ plugin as a database module in $GERRIT_SITE/lib(please note that +@PLUGIN plugin must be installed as a plugin and as a database module) and add +`installDbModule = com.ericsson.gerrit.plugins.highavailability.ValidationModule` +to the gerrit section in gerrit.config +* Install [global-refdb library](https://mvnrepository.com/artifact/com.gerritforge/global-refdb) as a library module in $GERRIT_SITE/lib and add +`installModule = com.gerritforge.gerrit.globalrefdb.validation.LibModule` to the gerrit +section in gerrit.config +* Install and configure [zookeeper-refdb plugin](https://gerrit-ci.gerritforge.com/view/Plugins-master/job/plugin-zookeeper-refdb-bazel-master) based on [config.md](https://gerrit.googlesource.com/plugins/zookeeper-refdb/+/refs/heads/master/src/main/resources/Documentation/config.md) +* Configure ref-database.enabled = true in @PLUGIN@.config to enable validation with +global-refdb. + +Here is an example of the minimal @PLUGIN@.config: + +Active instance one + +``` +[main] + sharedDirectory = /directory/accessible/from/both/instances + +[peerInfo "static"] + url = http://backupNodeHost1:8081/ + +[http] + user = username + password = password + +[ref-database] + enabled = true +``` + +Active instance two + +``` +[main] + sharedDirectory = /directory/accessible/from/both/instances + +[peerInfo "static"] + url = http://primaryNodeHost:8080/ + +[http] + user = username + password = password + +[ref-database] + enabled = true +``` + +Minimal zookeeper-refdb.config for both active instances: + +``` +[ref-database "zookeeper"] + connectString = zookeeperhost:2181 +``` \ No newline at end of file
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index f711fe3..2976de5 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -235,6 +235,32 @@ ```healthcheck.enable``` : Whether to enable the health check endpoint. Defaults to 'true'. +```ref-database.enabled``` +: Enable the use of a global ref-database. Defaults to 'false'. + +```ref-database.enforcementRules.<policy>``` +: Level of consistency enforcement across sites on a project:refs basis. + Supports two values for enforcing the policy on multiple projects or refs. + If the project or ref is omitted, apply the policy to all projects or all refs. + + The <policy> can be one of the following values: + + 1. REQUIRED - Throw an exception if a git ref-update is processed against + a local ref not yet in sync with the global ref-database. + The user transaction is cancelled. LOCK_FAILURE is reported upstream. + + 2. IGNORED - Do not validate against the global ref-database. + + *Example:* + ``` + [ref-database "enforcementRules"] + IGNORED = AProject:/refs/heads/feature + ``` + + Ignore the alignment with the global ref-db for AProject on refs/heads/feature. + + Defaults to no rule. All projects are REQUIRED to be consistent on all refs. + File 'gerrit.config' --------------------
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java index a9537bd..4bfac28 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -62,7 +62,6 @@ import static com.google.common.truth.Truth8.assertThat; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.when; import com.ericsson.gerrit.plugins.highavailability.Configuration.PeerInfoStrategy; import com.google.common.collect.ImmutableList; @@ -104,12 +103,11 @@ @Before public void setUp() throws IOException { globalPluginConfig = new Config(); - when(pluginConfigFactoryMock.getGlobalPluginConfig(PLUGIN_NAME)).thenReturn(globalPluginConfig); sitePaths = new SitePaths(SITE_PATH); } private Configuration getConfiguration() { - return new Configuration(pluginConfigFactoryMock, PLUGIN_NAME, sitePaths); + return new Configuration(globalPluginConfig, sitePaths); } @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java index 5843908..83f86d0 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java
@@ -25,15 +25,19 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static com.google.common.truth.Truth.assertThat; +import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.google.gerrit.acceptance.LightweightPluginDaemonTest; import com.google.gerrit.acceptance.NoHttpd; import com.google.gerrit.acceptance.TestPlugin; import com.google.gerrit.acceptance.UseLocalDisk; -import com.google.gerrit.acceptance.config.GlobalPluginConfig; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.http.HttpStatus; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -50,17 +54,23 @@ @Rule public WireMockRule wireMockRule = new WireMockRule(options().port(PORT)); + @Inject SitePaths sitePaths; + @Override public void setUpTestPlugin() throws Exception { givenThat(any(anyUrl()).willReturn(aResponse().withStatus(HttpStatus.SC_NO_CONTENT))); + FileBasedConfig fileBasedConfig = + new FileBasedConfig( + sitePaths.etc_dir.resolve(Configuration.PLUGIN_CONFIG_FILE).toFile(), FS.DETECTED); + fileBasedConfig.setString("peerInfo", "static", "url", URL); + fileBasedConfig.setInt("http", null, "retryInterval", 100); + fileBasedConfig.save(); beforeAction(); super.setUpTestPlugin(); } @Test @UseLocalDisk - @GlobalPluginConfig(pluginName = "high-availability", name = "peerInfo.static.url", value = URL) - @GlobalPluginConfig(pluginName = "high-availability", name = "http.retryInterval", value = "100") public void testIndexForwarding() throws Exception { String expectedRequest = getExpectedRequest(); CountDownLatch expectedRequestLatch = new CountDownLatch(1);