Merge branch 'stable-3.8' into stable-3.10 * stable-3.8: PushOne: Remove unused addRef() method TasksStorage: Remove synchronized from methods Place the replaying flag clearing in a finally Demote delete errors when distributor is enabled distributor: Reduce log level for no-op consolidations Change-Id: I15eb65d0f379a6e562d3c86e1089cfb7d9e6752c Release-Notes: skip
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 aa00634..267ba4e 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; @@ -306,13 +307,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); + } +}