Merge branch 'stable-3.8' into stable-3.10
* stable-3.8:
synchronizePendingEvents: Fix stuck replaying flag
Release-Notes: skip
Change-Id: I277e4bbfefee4895615976d1b26440d55a1eef58
diff --git a/BUILD b/BUILD
index ee97660..9c209ed 100644
--- a/BUILD
+++ b/BUILD
@@ -14,6 +14,23 @@
"Gerrit-SshModule: com.googlesource.gerrit.plugins.replication.SshModule",
],
resources = glob(["src/main/resources/**/*"]),
+ deps = [
+ "//plugins/replication:replication-api",
+ ],
+)
+
+gerrit_plugin(
+ name = "replication-api",
+ srcs = glob(
+ ["src/main/java/com/googlesource/gerrit/plugins/replication/api/*.java"],
+ ),
+ dir_name = "replication",
+ manifest_entries = [
+ "Implementation-Title: Replication plugin API",
+ "Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/replication",
+ "Gerrit-PluginName: replication-api",
+ "Gerrit-ApiModule: com.googlesource.gerrit.plugins.replication.api.ApiModule",
+ ],
)
junit_tests(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java
index a0d9624..2c049fc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java
@@ -21,6 +21,7 @@
import com.google.gerrit.server.git.WorkQueue;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
import java.nio.file.Path;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -42,10 +43,9 @@
public AutoReloadConfigDecorator(
@PluginName String pluginName,
WorkQueue workQueue,
- @MainReplicationConfig ReplicationConfig replicationConfig,
AutoReloadRunnable reloadRunner,
EventBus eventBus) {
- this.currentConfig = replicationConfig;
+ this.currentConfig = reloadRunner.getCurrentReplicationConfig();
this.autoReloadExecutor = workQueue.createQueue(1, pluginName + "_auto-reload-config");
this.reloadRunner = reloadRunner;
eventBus.register(this);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnable.java b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnable.java
index 71f7c67..a752a35 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnable.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnable.java
@@ -18,6 +18,7 @@
import com.google.common.flogger.FluentLogger;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
import java.util.List;
public class AutoReloadRunnable implements Runnable {
@@ -27,14 +28,14 @@
private final Provider<ObservableQueue> queueObserverProvider;
private final ConfigParser configParser;
private ReplicationConfig loadedConfig;
- private Provider<ReplicationConfig> replicationConfigProvider;
+ private Provider<ReplicationConfigImpl> replicationConfigProvider;
private String loadedConfigVersion;
private String lastFailedConfigVersion;
@Inject
public AutoReloadRunnable(
ConfigParser configParser,
- @MainReplicationConfig Provider<ReplicationConfig> replicationConfigProvider,
+ Provider<ReplicationConfigImpl> replicationConfigProvider,
EventBus eventBus,
Provider<ObservableQueue> queueObserverProvider) {
this.replicationConfigProvider = replicationConfigProvider;
@@ -60,6 +61,10 @@
reload();
}
+ public ReplicationConfig getCurrentReplicationConfig() {
+ return loadedConfig;
+ }
+
synchronized void reload() {
String pendingConfigVersion = loadedConfig.getVersion();
try {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java
index 5bae0af..e9409a6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java
@@ -19,6 +19,7 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
import java.io.IOException;
import java.nio.file.Files;
import java.util.concurrent.atomic.AtomicReference;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/CreateProjectTask.java b/src/main/java/com/googlesource/gerrit/plugins/replication/CreateProjectTask.java
index 2436fee..00f8baf 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/CreateProjectTask.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/CreateProjectTask.java
@@ -20,7 +20,7 @@
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig.FilterType;
import java.util.Optional;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
index 1f01e2a..c186493 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
@@ -15,7 +15,7 @@
package com.googlesource.gerrit.plugins.replication;
import static com.google.gerrit.server.project.ProjectCache.noSuchProject;
-import static com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig.replaceName;
+import static com.googlesource.gerrit.plugins.replication.ReplicationConfigImpl.replaceName;
import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.NON_EXISTING;
import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_OTHER_REASON;
@@ -71,8 +71,8 @@
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -127,8 +127,8 @@
}
public static class QueueInfo {
- public final Map<URIish, PushOne> pending;
- public final Map<URIish, PushOne> inFlight;
+ public final ImmutableMap<URIish, PushOne> pending;
+ public final ImmutableMap<URIish, PushOne> inFlight;
public QueueInfo(Map<URIish, PushOne> pending, Map<URIish, PushOne> inFlight) {
this.pending = ImmutableMap.copyOf(pending);
@@ -409,13 +409,13 @@
ReplicationState state,
boolean now,
boolean fromStorage) {
- Set<String> refsToSchedule = new HashSet<>();
+ ImmutableSet.Builder<String> toSchedule = ImmutableSet.builder();
for (String ref : refs) {
if (!shouldReplicate(project, ref, state)) {
repLog.atFine().log("Not scheduling replication %s:%s => %s", project, ref, uri);
continue;
}
- refsToSchedule.add(ref);
+ toSchedule.add(ref);
}
repLog.atInfo().log("scheduling replication %s:%s => %s", project, refs, uri);
@@ -444,11 +444,13 @@
}
}
+ ImmutableSet<String> refsToSchedule = toSchedule.build();
+ PushOne task;
synchronized (stateLock) {
- PushOne task = getPendingPush(uri);
+ task = getPendingPush(uri);
if (task == null) {
task = opFactory.create(project, uri);
- addRefs(task, ImmutableSet.copyOf(refsToSchedule));
+ task.addRefBatch(refsToSchedule);
task.addState(refsToSchedule, state);
@SuppressWarnings("unused")
ScheduledFuture<?> ignored =
@@ -458,7 +460,7 @@
"scheduled %s:%s => %s to run %s",
project, refsToSchedule, task, now ? "now" : "after " + config.getDelay() + "s");
} else {
- boolean added = addRefs(task, ImmutableSet.copyOf(refsToSchedule));
+ boolean added = task.addRefBatch(refsToSchedule);
task.addState(refsToSchedule, state);
String message = "consolidated %s:%s => %s with an existing pending push";
if (added || !fromStorage) {
@@ -471,6 +473,7 @@
state.increasePushTaskCount(project.get(), ref);
}
}
+ postReplicationScheduledEvent(task, refsToSchedule);
}
@Nullable
@@ -483,11 +486,13 @@
}
void pushWasCanceled(PushOne pushOp) {
+ Set<ImmutableSet<String>> notAttemptedRefs = Collections.emptySet();
synchronized (stateLock) {
URIish uri = pushOp.getURI();
pending.remove(uri);
- pushOp.notifyNotAttempted(pushOp.getRefs());
+ notAttemptedRefs = pushOp.getRefs();
}
+ pushOp.notifyNotAttempted(notAttemptedRefs);
}
void scheduleDeleteProject(URIish uri, Project.NameKey project, ProjectDeletionState state) {
@@ -504,12 +509,6 @@
pool.schedule(updateHeadFactory.create(uri, project, newHead), 0, TimeUnit.SECONDS);
}
- private boolean addRefs(PushOne e, ImmutableSet<String> refs) {
- boolean added = e.addRefBatch(refs);
- postReplicationScheduledEvent(e, refs);
- return added;
- }
-
/**
* It schedules again a PushOp instance.
*
@@ -532,6 +531,10 @@
* @param pushOp The PushOp instance to be scheduled.
*/
void reschedule(PushOne pushOp, RetryReason reason) {
+ boolean isRescheduled = false;
+ boolean isFailed = false;
+ RemoteRefUpdate.Status failedStatus = null;
+
synchronized (stateLock) {
URIish uri = pushOp.getURI();
PushOne pendingPushOp = getPendingPush(uri);
@@ -587,13 +590,13 @@
case TRANSPORT_ERROR:
case REPOSITORY_MISSING:
default:
- RemoteRefUpdate.Status status =
+ failedStatus =
RetryReason.REPOSITORY_MISSING.equals(reason)
? NON_EXISTING
: REJECTED_OTHER_REASON;
- postReplicationFailedEvent(pushOp, status);
+ isFailed = true;
if (pushOp.setToRetry()) {
- postReplicationScheduledEvent(pushOp);
+ isRescheduled = true;
replicationTasksStorage.get().reset(pushOp);
@SuppressWarnings("unused")
ScheduledFuture<?> ignored2 =
@@ -610,6 +613,12 @@
}
}
}
+ if (isFailed) {
+ postReplicationFailedEvent(pushOp, failedStatus);
+ }
+ if (isRescheduled) {
+ postReplicationScheduledEvent(pushOp);
+ }
}
RunwayStatus requestRunway(PushOne op) {
@@ -659,7 +668,7 @@
}
// by default push all projects
- List<String> projects = config.getProjects();
+ ImmutableList<String> projects = config.getProjects();
if (projects.isEmpty()) {
return true;
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationsCollection.java b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationsCollection.java
index 79a0683..8a41945 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationsCollection.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationsCollection.java
@@ -17,7 +17,7 @@
import static com.googlesource.gerrit.plugins.replication.AdminApiFactory.isGerrit;
import static com.googlesource.gerrit.plugins.replication.AdminApiFactory.isGerritHttp;
import static com.googlesource.gerrit.plugins.replication.AdminApiFactory.isSSH;
-import static com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig.replaceName;
+import static com.googlesource.gerrit.plugins.replication.ReplicationConfigImpl.replaceName;
import static com.googlesource.gerrit.plugins.replication.ReplicationQueue.repLog;
import static java.util.stream.Collectors.toList;
@@ -35,7 +35,8 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig.FilterType;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
@@ -52,7 +53,7 @@
private final Destination.Factory destinationFactory;
private final Provider<ReplicationQueue> replicationQueue;
- private volatile List<Destination> destinations;
+ private volatile ImmutableList<Destination> destinations;
private boolean shuttingDown;
public static class EventQueueNotEmptyException extends Exception {
@@ -258,7 +259,7 @@
}
}
- private List<Destination> allDestinations(
+ private ImmutableList<Destination> allDestinations(
Destination.Factory destinationFactory, List<RemoteConfiguration> remoteConfigurations) {
ImmutableList.Builder<Destination> dest = ImmutableList.builder();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/FanoutReplicationConfig.java b/src/main/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResource.java
similarity index 71%
rename from src/main/java/com/googlesource/gerrit/plugins/replication/FanoutReplicationConfig.java
rename to src/main/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResource.java
index b915d0d..4220ddb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/FanoutReplicationConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResource.java
@@ -21,7 +21,6 @@
import com.google.common.flogger.FluentLogger;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
-import com.google.gerrit.extensions.annotations.PluginData;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import java.io.IOException;
@@ -36,30 +35,26 @@
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
-public class FanoutReplicationConfig implements ReplicationConfig {
+public class FanoutConfigResource extends FileConfigResource {
+ public static String CONFIG_DIR = "replication";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private final ReplicationFileBasedConfig replicationConfig;
- private final Config config;
private final Path remoteConfigsDirPath;
@Inject
- public FanoutReplicationConfig(SitePaths site, @PluginData Path pluginDataDir)
- throws IOException, ConfigInvalidException {
-
- remoteConfigsDirPath = site.etc_dir.resolve("replication");
- replicationConfig = new ReplicationFileBasedConfig(site, pluginDataDir);
- config = replicationConfig.getConfig();
+ FanoutConfigResource(SitePaths site) throws IOException, ConfigInvalidException {
+ super(site);
+ this.remoteConfigsDirPath = site.etc_dir.resolve(CONFIG_DIR);
removeRemotes(config);
try (Stream<Path> files = Files.list(remoteConfigsDirPath)) {
files
.filter(Files::isRegularFile)
- .filter(FanoutReplicationConfig::isConfig)
- .map(FanoutReplicationConfig::loadConfig)
+ .filter(FanoutConfigResource::isConfig)
+ .map(FanoutConfigResource::loadConfig)
.filter(Optional::isPresent)
.map(Optional::get)
- .filter(FanoutReplicationConfig::isValid)
+ .filter(FanoutConfigResource::isValid)
.forEach(cfg -> addRemoteConfig(cfg, config));
} catch (IllegalStateException e) {
throw new ConfigInvalidException(e.getMessage());
@@ -122,53 +117,14 @@
}
@Override
- public boolean isReplicateAllOnPluginStart() {
- return replicationConfig.isReplicateAllOnPluginStart();
- }
-
- @Override
- public boolean isDefaultForceUpdate() {
- return replicationConfig.isDefaultForceUpdate();
- }
-
- @Override
- public int getMaxRefsToLog() {
- return replicationConfig.getMaxRefsToLog();
- }
-
- @Override
- public int getMaxRefsToShow() {
- return replicationConfig.getMaxRefsToShow();
- }
-
- @Override
- public Path getEventsDirectory() {
- return replicationConfig.getEventsDirectory();
- }
-
- @Override
- public int getSshConnectionTimeout() {
- return replicationConfig.getSshConnectionTimeout();
- }
-
- @Override
- public int getSshCommandTimeout() {
- return replicationConfig.getSshCommandTimeout();
- }
-
- @Override
- public int getDistributionInterval() {
- return replicationConfig.getDistributionInterval();
- }
-
- @Override
public String getVersion() {
+ String parentVersion = super.getVersion();
Hasher hasher = Hashing.murmur3_128().newHasher();
- hasher.putString(replicationConfig.getVersion(), UTF_8);
+ hasher.putString(parentVersion, UTF_8);
try (Stream<Path> files = Files.list(remoteConfigsDirPath)) {
files
.filter(Files::isRegularFile)
- .filter(FanoutReplicationConfig::isConfig)
+ .filter(FanoutConfigResource::isConfig)
.sorted()
.map(Path::toFile)
.map(FileSnapshot::save)
@@ -181,12 +137,7 @@
logger.atSevere().withCause(e).log(
"Cannot list remote configuration files from %s. Returning replication.config file version",
remoteConfigsDirPath);
- return replicationConfig.getVersion();
+ return parentVersion;
}
}
-
- @Override
- public Config getConfig() {
- return config;
- }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/FileConfigResource.java b/src/main/java/com/googlesource/gerrit/plugins/replication/FileConfigResource.java
new file mode 100644
index 0000000..baccb83
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/FileConfigResource.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2023 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.googlesource.gerrit.plugins.replication;
+
+import static com.googlesource.gerrit.plugins.replication.ReplicationQueue.repLog;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.common.UsedAt.Project;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.replication.api.ConfigResource;
+import java.io.IOException;
+import java.nio.file.Path;
+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;
+
+public class FileConfigResource implements ConfigResource {
+ public static final String CONFIG_NAME = "replication.config";
+ protected final FileBasedConfig config;
+
+ @Inject
+ @VisibleForTesting
+ @UsedAt(Project.PLUGIN_PULL_REPLICATION)
+ public FileConfigResource(SitePaths site) {
+ Path cfgPath = site.etc_dir.resolve(CONFIG_NAME);
+ this.config = new FileBasedConfig(cfgPath.toFile(), FS.DETECTED);
+ try {
+ config.load();
+ } catch (ConfigInvalidException e) {
+ repLog.atSevere().withCause(e).log("Config file %s is invalid: %s", cfgPath, e.getMessage());
+ } catch (IOException e) {
+ repLog.atSevere().withCause(e).log("Cannot read %s: %s", cfgPath, e.getMessage());
+ }
+ }
+
+ @Override
+ public Config getConfig() {
+ return config;
+ }
+
+ @Override
+ public String getVersion() {
+ return Long.toString(config.getFile().lastModified());
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java
index 9264d9b..8f4e0a1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java
@@ -23,7 +23,7 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig.FilterType;
import java.util.Collection;
import java.util.List;
import org.kohsuke.args4j.Option;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/MergedConfigResource.java b/src/main/java/com/googlesource/gerrit/plugins/replication/MergedConfigResource.java
new file mode 100644
index 0000000..43d90a5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/MergedConfigResource.java
@@ -0,0 +1,83 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.replication;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Suppliers;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.common.UsedAt.Project;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.util.Providers;
+import com.googlesource.gerrit.plugins.replication.api.ConfigResource;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfigOverrides;
+import java.util.function.Supplier;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+
+public class MergedConfigResource {
+ @VisibleForTesting
+ @UsedAt(Project.PLUGIN_PULL_REPLICATION)
+ public static MergedConfigResource withBaseOnly(ConfigResource base) {
+ MergedConfigResource mergedConfigResource = new MergedConfigResource();
+ mergedConfigResource.baseConfigProvider = Providers.of(base);
+ return mergedConfigResource;
+ }
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ @Inject private Provider<ConfigResource> baseConfigProvider;
+
+ private final Supplier<ConfigResource> base =
+ Suppliers.memoize(() -> this.baseConfigProvider.get());
+
+ @Inject(optional = true)
+ @Nullable
+ private DynamicItem<ReplicationConfigOverrides> overrides;
+
+ public Config getConfig() {
+ Config config = base.get().getConfig();
+ if (noOverrides()) {
+ return config;
+ }
+
+ String overridesText = overrides.get().getConfig().toText();
+ if (!overridesText.isEmpty()) {
+ try {
+ config.fromText(overridesText);
+ } catch (ConfigInvalidException e) {
+ logger.atWarning().withCause(e).log("Failed to merge replication config overrides");
+ }
+ }
+
+ return config;
+ }
+
+ public String getVersion() {
+ String baseVersion = base.get().getVersion();
+ if (noOverrides()) {
+ return baseVersion;
+ }
+
+ return baseVersion + overrides.get().getVersion();
+ }
+
+ private boolean noOverrides() {
+ return overrides == null || overrides.get() == null;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/Nfs.java b/src/main/java/com/googlesource/gerrit/plugins/replication/Nfs.java
deleted file mode 100644
index 09a632d..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/Nfs.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// 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.googlesource.gerrit.plugins.replication;
-
-import java.io.IOException;
-import java.util.Locale;
-
-/** Some NFS utilities */
-public class Nfs {
- /**
- * Determine if a throwable or a cause in its causal chain is a Stale NFS File Handle
- *
- * @return a boolean true if the throwable or a cause in its causal chain is a Stale NFS File
- * Handle
- */
- public static boolean isStaleFileHandleInCausalChain(Throwable throwable) {
- while (throwable != null) {
- if (throwable instanceof IOException && isStaleFileHandle((IOException) throwable)) {
- return true;
- }
- throwable = throwable.getCause();
- }
- return false;
- }
-
- /**
- * Determine if an IOException is a Stale NFS File Handle
- *
- * @return a boolean true if the IOException is a Stale NFS FIle Handle
- */
- public static boolean isStaleFileHandle(IOException ioe) {
- String msg = ioe.getMessage();
- return msg != null && msg.toLowerCase(Locale.ROOT).matches(".*stale .*file .*handle.*");
- }
-
- public static <T extends Throwable> void throwIfNotStaleFileHandle(T e) throws T {
- if (!isStaleFileHandleInCausalChain(e)) {
- throw e;
- }
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java b/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java
index 8b0aa3d..fc80781 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java
@@ -21,6 +21,7 @@
import com.google.gerrit.server.events.EventDispatcher;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.replication.PushResultProcessing.GitUpdateProcessing;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
index f9947ae..2b60145 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
@@ -54,6 +54,8 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationPushFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -122,7 +124,7 @@
private final Project.NameKey projectName;
private final URIish uri;
- private final Set<ImmutableSet<String>> refBatchesToPush = Sets.newHashSetWithExpectedSize(4);
+ private final Set<ImmutableSet<String>> refBatchesToPush = Sets.newConcurrentHashSet();
private boolean pushAllRefs;
private Repository git;
private boolean isCollision;
@@ -357,7 +359,7 @@
}
ReplicationState[] getStatesByRef(String ref) {
- Collection<ReplicationState> states = stateMap.get(ref);
+ List<ReplicationState> states = stateMap.get(ref);
return states.toArray(new ReplicationState[states.size()]);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java
index 05b4066..726bcf5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteConfiguration.java
@@ -14,7 +14,6 @@
package com.googlesource.gerrit.plugins.replication;
import com.google.common.collect.ImmutableList;
-import java.util.List;
import org.eclipse.jgit.transport.RemoteConfig;
/** Remote configuration for a replication endpoint */
@@ -117,7 +116,7 @@
* @return true, when configuration is for a single project, false otherwise
*/
default boolean isSingleProjectMatch() {
- List<String> projects = getProjects();
+ ImmutableList<String> projects = getProjects();
boolean ret = (projects.size() == 1);
if (ret) {
String projectMatch = projects.get(0);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigImpl.java
similarity index 67%
rename from src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
rename to src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigImpl.java
index 93990f4..1223db0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigImpl.java
@@ -13,50 +13,54 @@
// limitations under the License.
package com.googlesource.gerrit.plugins.replication;
-import static com.googlesource.gerrit.plugins.replication.ReplicationQueue.repLog;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.annotations.PluginData;
+import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
-import java.io.IOException;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
import java.nio.file.Path;
-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;
-public class ReplicationFileBasedConfig implements ReplicationConfig {
+public class ReplicationConfigImpl implements ReplicationConfig {
private static final int DEFAULT_SSH_CONNECTION_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes
private final SitePaths site;
- private Path cfgPath;
+ private final MergedConfigResource configResource;
private boolean replicateAllOnPluginStart;
private boolean defaultForceUpdate;
private int maxRefsToLog;
private final int maxRefsToShow;
private int sshCommandTimeout;
- private int sshConnectionTimeout = DEFAULT_SSH_CONNECTION_TIMEOUT_MS;
- private final FileBasedConfig config;
+ private int sshConnectionTimeout;
private final Path pluginDataDir;
+ private final Config config;
@Inject
- public ReplicationFileBasedConfig(SitePaths site, @PluginData Path pluginDataDir) {
+ public ReplicationConfigImpl(
+ MergedConfigResource configResource, SitePaths site, @PluginData Path pluginDataDir) {
this.site = site;
- this.cfgPath = site.etc_dir.resolve("replication.config");
- this.config = new FileBasedConfig(cfgPath.toFile(), FS.DETECTED);
- try {
- config.load();
- } catch (ConfigInvalidException e) {
- repLog.atSevere().withCause(e).log("Config file %s is invalid: %s", cfgPath, e.getMessage());
- } catch (IOException e) {
- repLog.atSevere().withCause(e).log("Cannot read %s: %s", cfgPath, e.getMessage());
- }
+ config = configResource.getConfig();
+ this.configResource = configResource;
this.replicateAllOnPluginStart = config.getBoolean("gerrit", "replicateOnStartup", false);
this.defaultForceUpdate = config.getBoolean("gerrit", "defaultForceUpdate", false);
this.maxRefsToLog = config.getInt("gerrit", "maxRefsToLog", 0);
this.maxRefsToShow = config.getInt("gerrit", "maxRefsToShow", 2);
+ this.sshCommandTimeout =
+ (int) ConfigUtil.getTimeUnit(config, "gerrit", null, "sshCommandTimeout", 0, SECONDS);
+ this.sshConnectionTimeout =
+ (int)
+ ConfigUtil.getTimeUnit(
+ config,
+ "gerrit",
+ null,
+ "sshConnectionTimeout",
+ DEFAULT_SSH_CONNECTION_TIMEOUT_MS,
+ MILLISECONDS);
this.pluginDataDir = pluginDataDir;
}
@@ -75,7 +79,7 @@
/**
* See {@link
- * com.googlesource.gerrit.plugins.replication.ReplicationConfig#isReplicateAllOnPluginStart()}
+ * com.googlesource.gerrit.plugins.replication.api.ReplicationConfig#isReplicateAllOnPluginStart()}
*/
@Override
public boolean isReplicateAllOnPluginStart() {
@@ -84,7 +88,7 @@
/**
* See {@link
- * com.googlesource.gerrit.plugins.replication.ReplicationConfig#isDefaultForceUpdate()}
+ * com.googlesource.gerrit.plugins.replication.api.ReplicationConfig#isDefaultForceUpdate()}
*/
@Override
public boolean isDefaultForceUpdate() {
@@ -93,7 +97,7 @@
@Override
public int getDistributionInterval() {
- return config.getInt("replication", "distributionInterval", 0);
+ return getConfig().getInt("replication", "distributionInterval", 0);
}
@Override
@@ -108,17 +112,13 @@
@Override
public Path getEventsDirectory() {
- String eventsDirectory = config.getString("replication", null, "eventsDirectory");
+ String eventsDirectory = getConfig().getString("replication", null, "eventsDirectory");
if (!Strings.isNullOrEmpty(eventsDirectory)) {
return site.resolve(eventsDirectory);
}
return pluginDataDir;
}
- Path getCfgPath() {
- return cfgPath;
- }
-
@Override
public Config getConfig() {
return config;
@@ -126,7 +126,7 @@
@Override
public String getVersion() {
- return Long.toString(config.getFile().lastModified());
+ return configResource.getVersion();
}
@Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigModule.java
new file mode 100644
index 0000000..b0343c9
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigModule.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2023 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.googlesource.gerrit.plugins.replication;
+
+import static com.googlesource.gerrit.plugins.replication.FanoutConfigResource.CONFIG_DIR;
+import static com.googlesource.gerrit.plugins.replication.FileConfigResource.CONFIG_NAME;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
+import com.google.inject.Scopes;
+import com.google.inject.internal.UniqueAnnotations;
+import com.googlesource.gerrit.plugins.replication.api.ConfigResource;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+public class ReplicationConfigModule extends AbstractModule {
+
+ private final SitePaths site;
+ private final Path cfgPath;
+
+ @Inject
+ ReplicationConfigModule(SitePaths site) {
+ this.site = site;
+ this.cfgPath = site.etc_dir.resolve(CONFIG_NAME);
+ }
+
+ @Override
+ protected void configure() {
+ bind(ConfigResource.class).to(getConfigResourceClass());
+
+ if (getReplicationConfig().getBoolean("gerrit", "autoReload", false)) {
+ bind(ReplicationConfig.class).to(AutoReloadConfigDecorator.class).in(Scopes.SINGLETON);
+ bind(LifecycleListener.class)
+ .annotatedWith(UniqueAnnotations.create())
+ .to(AutoReloadConfigDecorator.class);
+ } else {
+ bind(ReplicationConfig.class).to(ReplicationConfigImpl.class).in(Scopes.SINGLETON);
+ }
+ }
+
+ public FileBasedConfig getReplicationConfig() {
+ File replicationConfigFile = cfgPath.toFile();
+ FileBasedConfig config = new FileBasedConfig(replicationConfigFile, FS.DETECTED);
+ try {
+ config.load();
+ } catch (IOException | ConfigInvalidException e) {
+ throw new ProvisionException("Unable to load " + replicationConfigFile.getAbsolutePath(), e);
+ }
+ return config;
+ }
+
+ private Class<? extends ConfigResource> getConfigResourceClass() {
+ if (Files.exists(site.etc_dir.resolve(CONFIG_DIR))) {
+ return FanoutConfigResource.class;
+ }
+ return FileConfigResource.class;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationDestinations.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationDestinations.java
index 78c1a35..bcc07e5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationDestinations.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationDestinations.java
@@ -17,7 +17,7 @@
import com.google.common.collect.Multimap;
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.WorkQueue;
-import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig.FilterType;
import java.util.List;
import java.util.Optional;
import java.util.Set;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationExtensionPointModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationExtensionPointModule.java
index b92a54a..afbb54f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationExtensionPointModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationExtensionPointModule.java
@@ -14,8 +14,8 @@
package com.googlesource.gerrit.plugins.replication;
-import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.inject.AbstractModule;
+import com.googlesource.gerrit.plugins.replication.api.ApiModule;
/**
* Gerrit libModule for applying a ref-filter for outgoing replications.
@@ -27,6 +27,6 @@
@Override
protected void configure() {
- DynamicItem.itemOf(binder(), ReplicationPushFilter.class);
+ install(new ApiModule());
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
index 9f331f2..ac27280 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
@@ -24,11 +24,9 @@
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.events.ProjectDeletedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.events.EventTypes;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
-import com.google.inject.ProvisionException;
import com.google.inject.Scopes;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.internal.UniqueAnnotations;
@@ -40,29 +38,22 @@
import com.googlesource.gerrit.plugins.replication.events.RefReplicatedEvent;
import com.googlesource.gerrit.plugins.replication.events.RefReplicationDoneEvent;
import com.googlesource.gerrit.plugins.replication.events.ReplicationScheduledEvent;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
import org.apache.http.impl.client.CloseableHttpClient;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.transport.SshSessionFactory;
-import org.eclipse.jgit.util.FS;
class ReplicationModule extends AbstractModule {
- private final SitePaths site;
- private final Path cfgPath;
+
+ private final ReplicationConfigModule configModule;
@Inject
- public ReplicationModule(SitePaths site) {
- this.site = site;
- cfgPath = site.etc_dir.resolve("replication.config");
+ public ReplicationModule(ReplicationConfigModule configModule) {
+ this.configModule = configModule;
}
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(Destination.Factory.class));
+ install(configModule);
bind(ReplicationQueue.class).in(Scopes.SINGLETON);
bind(ObservableQueue.class).to(ReplicationQueue.class);
bind(LifecycleListener.class)
@@ -92,18 +83,6 @@
bind(ReplicationDestinations.class).to(DestinationsCollection.class);
bind(ConfigParser.class).to(DestinationConfigParser.class).in(Scopes.SINGLETON);
- if (getReplicationConfig().getBoolean("gerrit", "autoReload", false)) {
- bind(ReplicationConfig.class)
- .annotatedWith(MainReplicationConfig.class)
- .to(getReplicationConfigClass());
- bind(ReplicationConfig.class).to(AutoReloadConfigDecorator.class).in(Scopes.SINGLETON);
- bind(LifecycleListener.class)
- .annotatedWith(UniqueAnnotations.create())
- .to(AutoReloadConfigDecorator.class);
- } else {
- bind(ReplicationConfig.class).to(getReplicationConfigClass()).in(Scopes.SINGLETON);
- }
-
DynamicSet.setOf(binder(), ReplicationStateListener.class);
DynamicSet.bind(binder(), ReplicationStateListener.class).to(ReplicationStateLogger.class);
@@ -125,22 +104,4 @@
bind(TransportFactory.class).to(TransportFactoryImpl.class).in(Scopes.SINGLETON);
bind(CloseableHttpClient.class).toProvider(HttpClientProvider.class).in(Scopes.SINGLETON);
}
-
- private FileBasedConfig getReplicationConfig() {
- File replicationConfigFile = cfgPath.toFile();
- FileBasedConfig config = new FileBasedConfig(replicationConfigFile, FS.DETECTED);
- try {
- config.load();
- } catch (IOException | ConfigInvalidException e) {
- throw new ProvisionException("Unable to load " + replicationConfigFile.getAbsolutePath(), e);
- }
- return config;
- }
-
- private Class<? extends ReplicationConfig> getReplicationConfigClass() {
- if (Files.exists(site.etc_dir.resolve("replication"))) {
- return FanoutReplicationConfig.class;
- }
- return ReplicationFileBasedConfig.class;
- }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
index 4b5d7c1..6a36add 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
@@ -33,8 +33,9 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.googlesource.gerrit.plugins.replication.PushResultProcessing.GitUpdateProcessing;
-import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
import com.googlesource.gerrit.plugins.replication.ReplicationTasksStorage.ReplicateRefUpdate;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig.FilterType;
import com.googlesource.gerrit.plugins.replication.events.ProjectDeletionState;
import java.net.URISyntaxException;
import java.util.Collection;
@@ -311,13 +312,15 @@
private void fireBeforeStartupEvents() {
Set<String> eventsReplayed = new HashSet<>();
- for (ReferencesUpdatedEvent event : beforeStartupEventsQueue) {
+ ReferencesUpdatedEvent event;
+ while ((event = beforeStartupEventsQueue.peek()) != null) {
String eventKey = String.format("%s:%s", event.projectName(), event.getRefNames());
if (!eventsReplayed.contains(eventKey)) {
repLog.atInfo().log("Firing pending task %s", event);
fire(event.projectName(), event.updatedRefs());
eventsReplayed.add(eventKey);
}
+ beforeStartupEventsQueue.remove(event);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationTasksStorage.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationTasksStorage.java
index 2e4b619..40f5278 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationTasksStorage.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationTasksStorage.java
@@ -33,6 +33,7 @@
import com.google.inject.Inject;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/SshHelper.java b/src/main/java/com/googlesource/gerrit/plugins/replication/SshHelper.java
index d73c101..6f5ba1e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/SshHelper.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/SshHelper.java
@@ -16,6 +16,7 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
import java.io.IOException;
import java.io.OutputStream;
import org.eclipse.jgit.errors.TransportException;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/SshModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/SshModule.java
index 0cab7b1..a66cce6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/SshModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/SshModule.java
@@ -14,9 +14,16 @@
package com.googlesource.gerrit.plugins.replication;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.sshd.PluginCommandModule;
+import com.google.inject.Inject;
class SshModule extends PluginCommandModule {
+ @Inject
+ SshModule(@PluginName String pluginName) {
+ super(pluginName);
+ }
+
@Override
protected void configureCommands() {
command(StartCommand.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/api/ApiModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ApiModule.java
new file mode 100644
index 0000000..3764ede
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ApiModule.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.replication.api;
+
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.inject.AbstractModule;
+
+public class ApiModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ DynamicItem.itemOf(binder(), ReplicationPushFilter.class);
+ DynamicItem.itemOf(binder(), ReplicationConfigOverrides.class);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/api/ConfigResource.java b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ConfigResource.java
new file mode 100644
index 0000000..43733bc
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ConfigResource.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 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.googlesource.gerrit.plugins.replication.api;
+
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Separates configuration resource from the configuration schema.
+ *
+ * <p>The {@code ConfigResource} splits configuration source from configuration options. The
+ * resource is just an {@code Config} object with its version. The configuration options can be
+ * loaded from different sources eg. a single file, multiple files or ZooKeeper.
+ *
+ * <p>Here we don't assume any configuration options being present in the {@link Config} object,
+ * this is the responsibility of {@link ReplicationConfig} interface. Think about {@code
+ * ConfigResource} as a "plain text file" and {@code ReplicationConfig} as a XML file.
+ */
+public interface ConfigResource {
+
+ /**
+ * Configuration for the plugin and all replication end points.
+ *
+ * @return current configuration
+ */
+ Config getConfig();
+
+ /**
+ * Current logical version string of the current configuration on the persistent storage.
+ *
+ * @return latest logical version number on the persistent storage
+ */
+ String getVersion();
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ReplicationConfig.java
similarity index 97%
rename from src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java
rename to src/main/java/com/googlesource/gerrit/plugins/replication/api/ReplicationConfig.java
index ec75450..8ef4e2b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ReplicationConfig.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.googlesource.gerrit.plugins.replication;
+package com.googlesource.gerrit.plugins.replication.api;
import java.nio.file.Path;
import org.eclipse.jgit.lib.Config;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/MainReplicationConfig.java b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ReplicationConfigOverrides.java
similarity index 61%
rename from src/main/java/com/googlesource/gerrit/plugins/replication/MainReplicationConfig.java
rename to src/main/java/com/googlesource/gerrit/plugins/replication/api/ReplicationConfigOverrides.java
index e8d95ec..b6fbc1a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/MainReplicationConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ReplicationConfigOverrides.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2024 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.
@@ -12,12 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.googlesource.gerrit.plugins.replication;
+package com.googlesource.gerrit.plugins.replication.api;
-import com.google.inject.BindingAnnotation;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-@BindingAnnotation
-@Retention(RetentionPolicy.RUNTIME)
-public @interface MainReplicationConfig {}
+/**
+ * Provide a way to override or extend replication configuration from other sources, like git
+ * repository or external configuration management tool.
+ */
+public interface ReplicationConfigOverrides extends ConfigResource {}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationPushFilter.java b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ReplicationPushFilter.java
similarity index 94%
rename from src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationPushFilter.java
rename to src/main/java/com/googlesource/gerrit/plugins/replication/api/ReplicationPushFilter.java
index eb6ba90..afbdff6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationPushFilter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/api/ReplicationPushFilter.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.googlesource.gerrit.plugins.replication;
+package com.googlesource.gerrit.plugins.replication.api;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import java.util.List;
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 8f1ca86..6f9a49d 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -302,7 +302,7 @@
to create/remove projects and update repository HEAD references.
NOTE: In order to replicate project deletion, the
- link:https://gerrit-review.googlesource.com/admin/projects/plugins/delete-project delete-project[delete-project]
+ [delete-project](https://gerrit-review.googlesource.com/admin/projects/plugins/delete-project)
plugin must be installed on the other Gerrit.
*Backward compatibility notice*
diff --git a/src/main/resources/Documentation/extension-point.md b/src/main/resources/Documentation/extension-point.md
index f6579fa..fab5fbc 100644
--- a/src/main/resources/Documentation/extension-point.md
+++ b/src/main/resources/Documentation/extension-point.md
@@ -26,19 +26,6 @@
Extension points
----------------
-* `com.googlesource.gerrit.plugins.replication.ReplicationPushFilter`
-
- Filter out the ref updates pushed to a remote instance.
- Only one filter at a time is supported. Filter implementation needs to bind a `DynamicItem`.
-
- Default: no filtering
-
- Example:
-
- ```java
- DynamicItem.bind(binder(), ReplicationPushFilter.class).to(ReplicationPushFilterImpl.class);
- ```
-
* `com.googlesource.gerrit.plugins.replication.AdminApiFactory`
Create an instance of `AdminApi` for a given remote URL. The default implementation
@@ -52,3 +39,72 @@
```java
DynamicItem.bind(binder(), AdminApiFactory.class).to(AdminApiFactoryImpl.class);
```
+
+@PLUGIN@ Cross Plugin Communication
+===================================
+
+The @PLUGIN@ plugin exposes _ApiModule_ that allows to provide _Cross Plugin
+Communication_. Extension points can be defined from the replication plugin when it is loaded
+as [ApiModule](../../../Documentation/dev-plugins.html#_cross_plugin_communication) and
+implemented by another plugin by declaring a `provided` dependency from the replication plugin api.
+
+Build the @plugin@'s API jar
+----------------------------
+
+The replication plugin's extension points are defined in the `c.g.g.p.r.a.ApiModule`
+that needs to be built from source and loaded in Gerrit as ApiModule.
+
+```
+$ bazelisk build plugins/replication:replication-api
+$ cp bazel-bin/plugins/replication/replication-api.jar $GERRIT_SITE/plugins
+```
+
+> **NOTE**: Use and configuration of the replication-api as ApiModule is compatible with
+> Gerrit v3.9 onwards and requires a Gerrit server restart; it does not support hot plugin install
+> or upgrade.
+
+
+Setup
+-----
+
+Check the [official documentation](https://gerrit-review.googlesource.com/Documentation/dev-plugins.html#_cross_plugin_communication)
+on how to setup your project.
+
+Working with [Extension Points](./extension-point.md)
+-----------------------------------------------------
+
+In order to use both, the _Cross Plugin Communication_ and replication
+_Extension Points_, follow the [Install extension libModule](./extension-point.md#install-extension-libmodule)
+steps and make sure that `replication.jar` is only present in `lib/` directory.
+
+Exposed API
+-----------
+
+* `com.googlesource.gerrit.plugins.replication.api.ReplicationConfigOverrides`
+
+ Override current replication configuration from external source (eg. git
+ repository, ZooKeeper).
+
+ Replication plugin will still use configuration from `$gerrit_site/etc/`, but
+ with overrides it can be modified dynamically from external source, similarly to
+ how `git config` uses _user_ and _repository_ configuration files.
+
+ Only one override at a time is supported. The implementation needs to bind a
+ `DynamicItem`.
+
+ ```java
+ DynamicItem.bind(binder(), ReplicationConfigOverrides.class).to(ReplicationConfigOverridesImpl.class);
+ ```
+
+* `com.googlesource.gerrit.plugins.replication.api.ReplicationPushFilter`
+
+ Filter out the ref updates pushed to a remote instance.
+ Only one filter at a time is supported. Filter implementation needs to bind a `DynamicItem`.
+
+ Default: no filtering
+
+ Example:
+
+ ```java
+ DynamicItem.bind(binder(), ReplicationPushFilter.class).to(ReplicationPushFilterImpl.class);
+ ```
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/AbstractConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/AbstractConfigTest.java
index 2b6a8c4..b8bbc08 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/AbstractConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/AbstractConfigTest.java
@@ -27,6 +27,8 @@
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.util.Providers;
+import com.googlesource.gerrit.plugins.replication.api.ConfigResource;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
@@ -128,6 +130,13 @@
assertThatIsDestination(matchingDestinations.get(0), remoteName, remoteUrls);
}
+ protected DestinationsCollection newDestinationsCollections(ConfigResource configResource)
+ throws ConfigInvalidException {
+ return newDestinationsCollections(
+ new ReplicationConfigImpl(
+ MergedConfigResource.withBaseOnly(configResource), sitePaths, pluginDataPath));
+ }
+
protected DestinationsCollection newDestinationsCollections(ReplicationConfig replicationConfig)
throws ConfigInvalidException {
return new DestinationsCollection(
@@ -138,7 +147,10 @@
eventBus);
}
- protected ReplicationConfig newReplicationFileBasedConfig() {
- return new ReplicationFileBasedConfig(sitePaths, pluginDataPath);
+ protected ReplicationConfigImpl newReplicationFileBasedConfig() {
+ return new ReplicationConfigImpl(
+ MergedConfigResource.withBaseOnly(new FileConfigResource(sitePaths)),
+ sitePaths,
+ pluginDataPath);
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecoratorTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecoratorTest.java
index b1b9453..021707a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecoratorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecoratorTest.java
@@ -18,7 +18,8 @@
import com.google.inject.Provider;
import com.google.inject.util.Providers;
-import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig.FilterType;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -75,12 +76,19 @@
remoteConfig.setString("remote", null, "url", remoteUrl1);
remoteConfig.save();
- replicationConfig = new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ replicationConfig =
+ new ReplicationConfigImpl(
+ MergedConfigResource.withBaseOnly(new FanoutConfigResource(sitePaths)),
+ sitePaths,
+ pluginDataPath);
newAutoReloadConfig(
() -> {
try {
- return new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ return new ReplicationConfigImpl(
+ MergedConfigResource.withBaseOnly(new FanoutConfigResource(sitePaths)),
+ sitePaths,
+ pluginDataPath);
} catch (IOException | ConfigInvalidException e) {
throw new RuntimeException(e);
}
@@ -122,12 +130,19 @@
remoteConfig.setString("remote", null, "url", remoteUrl2);
remoteConfig.save();
- replicationConfig = new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ replicationConfig =
+ new ReplicationConfigImpl(
+ MergedConfigResource.withBaseOnly(new FanoutConfigResource(sitePaths)),
+ sitePaths,
+ pluginDataPath);
newAutoReloadConfig(
() -> {
try {
- return new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ return new ReplicationConfigImpl(
+ MergedConfigResource.withBaseOnly(new FanoutConfigResource(sitePaths)),
+ sitePaths,
+ pluginDataPath);
} catch (IOException | ConfigInvalidException e) {
throw new RuntimeException(e);
}
@@ -168,12 +183,19 @@
remoteConfig.setString("remote", null, "url", remoteUrl2);
remoteConfig.save();
- replicationConfig = new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ replicationConfig =
+ new ReplicationConfigImpl(
+ MergedConfigResource.withBaseOnly(new FanoutConfigResource(sitePaths)),
+ sitePaths,
+ pluginDataPath);
newAutoReloadConfig(
() -> {
try {
- return new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ return new ReplicationConfigImpl(
+ MergedConfigResource.withBaseOnly(new FanoutConfigResource(sitePaths)),
+ sitePaths,
+ pluginDataPath);
} catch (IOException | ConfigInvalidException e) {
throw new RuntimeException(e);
}
@@ -228,24 +250,20 @@
}
private AutoReloadConfigDecorator newAutoReloadConfig(
- Supplier<ReplicationConfig> configSupplier) {
+ Supplier<ReplicationConfigImpl> configSupplier) {
AutoReloadRunnable autoReloadRunnable =
new AutoReloadRunnable(
configParser,
- new Provider<ReplicationConfig>() {
+ new Provider<ReplicationConfigImpl>() {
@Override
- public ReplicationConfig get() {
+ public ReplicationConfigImpl get() {
return configSupplier.get();
}
},
eventBus,
Providers.of(replicationQueueMock));
return new AutoReloadConfigDecorator(
- "replication",
- workQueueMock,
- newReplicationFileBasedConfig(),
- autoReloadRunnable,
- eventBus);
+ "replication", workQueueMock, autoReloadRunnable, eventBus);
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnableTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnableTest.java
index 725052c..2040edb 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnableTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnableTest.java
@@ -81,11 +81,14 @@
autoReloadRunnable.run();
}
- private Provider<ReplicationConfig> newVersionConfigProvider() {
+ private Provider<ReplicationConfigImpl> newVersionConfigProvider() {
return new Provider<>() {
@Override
- public ReplicationConfig get() {
- return new ReplicationFileBasedConfig(sitePaths, sitePaths.data_dir) {
+ public ReplicationConfigImpl get() {
+ return new ReplicationConfigImpl(
+ MergedConfigResource.withBaseOnly(new FileConfigResource(sitePaths)),
+ sitePaths,
+ sitePaths.data_dir) {
@Override
public String getVersion() {
return String.format("%s", System.nanoTime());
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/FanoutReplicationConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResourceTest.java
similarity index 81%
rename from src/test/java/com/googlesource/gerrit/plugins/replication/FanoutReplicationConfigTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResourceTest.java
index 8cba4bb..9147ea1 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/FanoutReplicationConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/FanoutConfigResourceTest.java
@@ -16,9 +16,11 @@
import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;
import static com.google.common.truth.Truth.assertThat;
+import static com.googlesource.gerrit.plugins.replication.FanoutConfigResource.CONFIG_DIR;
import com.google.common.io.MoreFiles;
-import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig.FilterType;
+import java.io.File;
import java.io.IOException;
import java.util.List;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -27,9 +29,9 @@
import org.junit.Before;
import org.junit.Test;
-public class FanoutReplicationConfigTest extends AbstractConfigTest {
+public class FanoutConfigResourceTest extends AbstractConfigTest {
- public FanoutReplicationConfigTest() throws IOException {
+ public FanoutConfigResourceTest() throws IOException {
super();
}
@@ -39,13 +41,19 @@
String remoteUrl2 = "ssh://git@git.elsewhere.com/${name}";
@Before
- public void setupTests() {
+ public void setupTests() throws Exception {
FileBasedConfig config = newReplicationConfig();
try {
config.save();
} catch (IOException e) {
throw new RuntimeException(e);
}
+ File replicationConfig = sitePaths.etc_dir.resolve(CONFIG_DIR).toFile();
+ if (!replicationConfig.mkdir()) {
+ throw new IOException(
+ "Cannot create test replication config directory in: "
+ + replicationConfig.toPath().toAbsolutePath());
+ }
}
@Test
@@ -62,7 +70,7 @@
config.save();
DestinationsCollection destinationsCollections =
- newDestinationsCollections(new FanoutReplicationConfig(sitePaths, pluginDataPath));
+ newDestinationsCollections(new FanoutConfigResource(sitePaths));
List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(1);
@@ -81,7 +89,7 @@
config.save();
DestinationsCollection destinationsCollections =
- newDestinationsCollections(new FanoutReplicationConfig(sitePaths, pluginDataPath));
+ newDestinationsCollections(new FanoutConfigResource(sitePaths));
List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(2);
@@ -101,7 +109,7 @@
config.save();
DestinationsCollection destinationsCollections =
- newDestinationsCollections(new FanoutReplicationConfig(sitePaths, pluginDataPath));
+ newDestinationsCollections(new FanoutConfigResource(sitePaths));
List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(1);
@@ -123,7 +131,7 @@
config.save();
DestinationsCollection destinationsCollections =
- newDestinationsCollections(new FanoutReplicationConfig(sitePaths, pluginDataPath));
+ newDestinationsCollections(new FanoutConfigResource(sitePaths));
List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(1);
@@ -136,7 +144,7 @@
config.setString("remote", null, "url", "ssh://git@git.elsewhere.com/name");
config.save();
- newDestinationsCollections(new FanoutReplicationConfig(sitePaths, pluginDataPath));
+ newDestinationsCollections(new FanoutConfigResource(sitePaths));
}
@Test
@@ -145,7 +153,7 @@
config.save();
DestinationsCollection destinationsCollections =
- newDestinationsCollections(new FanoutReplicationConfig(sitePaths, pluginDataPath));
+ newDestinationsCollections(new FanoutConfigResource(sitePaths));
List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(0);
}
@@ -158,7 +166,7 @@
config.save();
DestinationsCollection destinationsCollections =
- newDestinationsCollections(new FanoutReplicationConfig(sitePaths, pluginDataPath));
+ newDestinationsCollections(new FanoutConfigResource(sitePaths));
List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(0);
}
@@ -170,7 +178,7 @@
config.save();
DestinationsCollection destinationsCollections =
- newDestinationsCollections(new FanoutReplicationConfig(sitePaths, pluginDataPath));
+ newDestinationsCollections(new FanoutConfigResource(sitePaths));
List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(0);
}
@@ -186,12 +194,11 @@
config.setString("remote", null, "url", remoteUrl2);
config.save();
- FanoutReplicationConfig objectUnderTest =
- new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ FanoutConfigResource objectUnderTest = new FanoutConfigResource(sitePaths);
String version = objectUnderTest.getVersion();
- objectUnderTest = new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ objectUnderTest = new FanoutConfigResource(sitePaths);
assertThat(objectUnderTest.getVersion()).isEqualTo(version);
}
@@ -203,8 +210,7 @@
config.setString("remote", null, "url", remoteUrl1);
config.save();
- FanoutReplicationConfig objectUnderTest =
- new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ FanoutConfigResource objectUnderTest = new FanoutConfigResource(sitePaths);
String version = objectUnderTest.getVersion();
@@ -222,8 +228,7 @@
config.setString("remote", null, "url", remoteUrl1);
config.save();
- FanoutReplicationConfig objectUnderTest =
- new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ FanoutConfigResource objectUnderTest = new FanoutConfigResource(sitePaths);
String version = objectUnderTest.getVersion();
@@ -244,8 +249,7 @@
config.setString("remote", null, "url", remoteUrl2);
config.save();
- FanoutReplicationConfig objectUnderTest =
- new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ FanoutConfigResource objectUnderTest = new FanoutConfigResource(sitePaths);
String version = objectUnderTest.getVersion();
assertThat(
@@ -267,11 +271,14 @@
config.setString("remote", null, "url", remoteUrl2);
config.save();
- FanoutReplicationConfig objectUnderTest =
- new FanoutReplicationConfig(sitePaths, pluginDataPath);
+ FanoutConfigResource objectUnderTest = new FanoutConfigResource(sitePaths);
String replicationConfigVersion =
- new ReplicationFileBasedConfig(sitePaths, pluginDataPath).getVersion();
+ new ReplicationConfigImpl(
+ MergedConfigResource.withBaseOnly(new FileConfigResource(sitePaths)),
+ sitePaths,
+ pluginDataPath)
+ .getVersion();
MoreFiles.deleteRecursively(sitePaths.etc_dir.resolve("replication"), ALLOW_INSECURE);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/MergedConfigResourceTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/MergedConfigResourceTest.java
new file mode 100644
index 0000000..18bb603
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/MergedConfigResourceTest.java
@@ -0,0 +1,116 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.replication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.googlesource.gerrit.plugins.replication.api.ApiModule;
+import com.googlesource.gerrit.plugins.replication.api.ConfigResource;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfigOverrides;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+public class MergedConfigResourceTest {
+ private static final int BASE_CONFIG_MAX_RETIRES = 10;
+ private static final int OVERRIDDEN_CONFIG_MAX_RETIRES = 5;
+
+ @Test
+ public void onlyUseBaseConfig() {
+ final MergedConfigResource configResource = newMergedConfigResource();
+
+ assertThat(configResource.getVersion()).isEqualTo("base");
+ assertThat(getMaxRetires(configResource)).isEqualTo(BASE_CONFIG_MAX_RETIRES);
+ }
+
+ @Test
+ public void overrideBaseConfig() {
+ final MergedConfigResource configResource =
+ newMergedConfigResource(TestReplicationConfigOverrides.class);
+
+ assertThat(configResource.getVersion()).isEqualTo("baseoverride");
+ assertThat(getMaxRetires(configResource)).isEqualTo(OVERRIDDEN_CONFIG_MAX_RETIRES);
+ assertThat(getUseGcClient(configResource)).isTrue();
+ }
+
+ private MergedConfigResource newMergedConfigResource() {
+ return newMergedConfigResource(null);
+ }
+
+ private MergedConfigResource newMergedConfigResource(
+ Class<? extends ReplicationConfigOverrides> overrides) {
+ return Guice.createInjector(
+ new AbstractModule() {
+ @Override
+ protected void configure() {
+ install(new ApiModule());
+
+ bind(ConfigResource.class).to(TestBaseConfigResource.class);
+
+ if (overrides != null) {
+ DynamicItem.bind(binder(), ReplicationConfigOverrides.class).to(overrides);
+ }
+ }
+ })
+ .getInstance(MergedConfigResource.class);
+ }
+
+ private static class TestBaseConfigResource implements ConfigResource {
+ @Override
+ public Config getConfig() {
+ Config config = new Config();
+ setMaxRetires(config, BASE_CONFIG_MAX_RETIRES);
+ return config;
+ }
+
+ @Override
+ public String getVersion() {
+ return "base";
+ }
+ }
+
+ private static class TestReplicationConfigOverrides implements ReplicationConfigOverrides {
+ @Override
+ public Config getConfig() {
+ Config config = new Config();
+ setMaxRetires(config, OVERRIDDEN_CONFIG_MAX_RETIRES);
+ setUseGcClient(config, true);
+ return config;
+ }
+
+ @Override
+ public String getVersion() {
+ return "override";
+ }
+ }
+
+ private static void setMaxRetires(Config config, int value) {
+ config.setInt("replication", null, "maxRetries", value);
+ }
+
+ private static void setUseGcClient(Config config, boolean value) {
+ config.setBoolean("replication", null, "useGcClient", value);
+ }
+
+ private static int getMaxRetires(MergedConfigResource resource) {
+ return resource.getConfig().getInt("replication", null, "maxRetries", -1);
+ }
+
+ private static boolean getUseGcClient(MergedConfigResource resource) {
+ return resource.getConfig().getBoolean("replication", null, "useGcClient", false);
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/PushOneTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/PushOneTest.java
index 21fe85c..aa29846 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/PushOneTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/PushOneTest.java
@@ -39,6 +39,8 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.IdGenerator;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationPushFilter;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigImplTest.java
similarity index 93%
rename from src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfigTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigImplTest.java
index 79b05cc..993e408 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationConfigImplTest.java
@@ -16,15 +16,15 @@
import static com.google.common.truth.Truth.assertThat;
-import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig.FilterType;
import java.io.IOException;
import java.util.List;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.junit.Test;
-public class ReplicationFileBasedConfigTest extends AbstractConfigTest {
+public class ReplicationConfigImplTest extends AbstractConfigTest {
- public ReplicationFileBasedConfigTest() throws IOException {
+ public ReplicationConfigImplTest() throws IOException {
super();
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationDaemon.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationDaemon.java
index 3bc86c7..ba4a958 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationDaemon.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationDaemon.java
@@ -49,7 +49,7 @@
@UseLocalDisk
@TestPlugin(
name = "replication",
- sysModule = "com.googlesource.gerrit.plugins.replication.ReplicationModule")
+ sysModule = "com.googlesource.gerrit.plugins.replication.TestReplicationModule")
public class ReplicationDaemon extends LightweightPluginDaemonTest {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
protected static final Optional<String> ALL_PROJECTS = Optional.empty();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationDistributorIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationDistributorIT.java
index dfcf250..dc73036 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationDistributorIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationDistributorIT.java
@@ -39,7 +39,7 @@
@UseLocalDisk
@TestPlugin(
name = "replication",
- sysModule = "com.googlesource.gerrit.plugins.replication.ReplicationModule")
+ sysModule = "com.googlesource.gerrit.plugins.replication.TestReplicationModule")
public class ReplicationDistributorIT extends ReplicationStorageDaemon {
private static final int TEST_DISTRIBUTION_INTERVAL_SECONDS = 3;
private static final int TEST_DISTRIBUTION_DURATION_SECONDS = 1;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationEventsIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationEventsIT.java
index b32829c..f31d013 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationEventsIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationEventsIT.java
@@ -58,7 +58,7 @@
@Sandboxed
@TestPlugin(
name = "replication",
- sysModule = "com.googlesource.gerrit.plugins.replication.ReplicationModule")
+ sysModule = "com.googlesource.gerrit.plugins.replication.TestReplicationModule")
public class ReplicationEventsIT extends ReplicationDaemon {
private static final Duration TEST_POST_EVENT_TIMEOUT = Duration.ofSeconds(1);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFanoutIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFanoutIT.java
index 5f80e8c..0d222b7 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFanoutIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFanoutIT.java
@@ -43,7 +43,7 @@
@UseLocalDisk
@TestPlugin(
name = "replication",
- sysModule = "com.googlesource.gerrit.plugins.replication.ReplicationModule")
+ sysModule = "com.googlesource.gerrit.plugins.replication.TestReplicationModule")
public class ReplicationFanoutIT extends ReplicationDaemon {
private ReplicationTasksStorage tasksStorage;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
index 9285c58..4414cec 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
@@ -52,7 +52,7 @@
@UseLocalDisk
@TestPlugin(
name = "replication",
- sysModule = "com.googlesource.gerrit.plugins.replication.ReplicationModule")
+ sysModule = "com.googlesource.gerrit.plugins.replication.TestReplicationModule")
public class ReplicationIT extends ReplicationDaemon {
private static final int TEST_REPLICATION_DELAY = 1;
private static final int TEST_REPLICATION_RETRY = 1;
@@ -371,7 +371,6 @@
Result pushResult = createChange();
shutdownDestinations();
- pushResult.getCommit();
String sourceRef = pushResult.getPatchSet().refName();
assertThrows(
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationPushInBatchesIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationPushInBatchesIT.java
index cf8dbe3..67caae9 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationPushInBatchesIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationPushInBatchesIT.java
@@ -29,7 +29,7 @@
@TestPlugin(
name = "replication",
- sysModule = "com.googlesource.gerrit.plugins.replication.ReplicationModule")
+ sysModule = "com.googlesource.gerrit.plugins.replication.TestReplicationModule")
public class ReplicationPushInBatchesIT extends ReplicationDaemon {
@Override
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageDaemon.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageDaemon.java
index b6d14e8..a6ca305 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageDaemon.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageDaemon.java
@@ -16,6 +16,7 @@
import static java.util.stream.Collectors.toList;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageIT.java
index 9390798..e800cff 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageIT.java
@@ -23,8 +23,8 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.googlesource.gerrit.plugins.replication.Destination.QueueInfo;
-import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
import com.googlesource.gerrit.plugins.replication.ReplicationTasksStorage.ReplicateRefUpdate;
+import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig.FilterType;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
@@ -49,7 +49,7 @@
@UseLocalDisk
@TestPlugin(
name = "replication",
- sysModule = "com.googlesource.gerrit.plugins.replication.ReplicationModule")
+ sysModule = "com.googlesource.gerrit.plugins.replication.TestReplicationModule")
public class ReplicationStorageIT extends ReplicationStorageDaemon {
@Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageMPIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageMPIT.java
index 1001e6c..45cf310 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageMPIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageMPIT.java
@@ -34,7 +34,7 @@
@UseLocalDisk
@TestPlugin(
name = "replication",
- sysModule = "com.googlesource.gerrit.plugins.replication.ReplicationModule")
+ sysModule = "com.googlesource.gerrit.plugins.replication.TestReplicationModule")
public class ReplicationStorageMPIT extends ReplicationStorageDaemon {
@Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/TestReplicationModule.java b/src/test/java/com/googlesource/gerrit/plugins/replication/TestReplicationModule.java
new file mode 100644
index 0000000..a01a2cf
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/TestReplicationModule.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.replication;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.replication.api.ApiModule;
+
+public class TestReplicationModule extends AbstractModule {
+ private final ReplicationModule replicationModule;
+
+ @Inject
+ TestReplicationModule(ReplicationModule replicationModule) {
+ this.replicationModule = replicationModule;
+ }
+
+ @Override
+ protected void configure() {
+ install(new ApiModule());
+ install(replicationModule);
+ }
+}