Merge branch 'stable-3.0'
* stable-3.0:
Clean up ReplicationQueue tests
Convert ReferenceUpdateEvent class to AutoValue
Fix issue with dropping events on start
Change-Id: I5a4bc84a829600865fdd551152219cd1bbe5aa91
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 945f869..4c07f40 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java
@@ -14,127 +14,45 @@
package com.googlesource.gerrit.plugins.replication;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Multimap;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.FileUtil;
-import com.google.gerrit.extensions.annotations.PluginData;
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.server.git.WorkQueue;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.io.IOException;
import java.nio.file.Path;
-import java.util.List;
-import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.transport.URIish;
@Singleton
-public class AutoReloadConfigDecorator implements ReplicationConfig {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+public class AutoReloadConfigDecorator implements ReplicationConfig, LifecycleListener {
private static final long RELOAD_DELAY = 120;
private static final long RELOAD_INTERVAL = 60;
private volatile ReplicationFileBasedConfig currentConfig;
- private long currentConfigTs;
- private long lastFailedConfigTs;
- private final SitePaths site;
- private final Destination.Factory destinationFactory;
- private final Path pluginDataDir;
- // Use Provider<> instead of injecting the ReplicationQueue because of circular dependency with
- // ReplicationConfig
- private final Provider<ReplicationQueue> replicationQueue;
private final ScheduledExecutorService autoReloadExecutor;
private ScheduledFuture<?> autoReloadRunnable;
-
- private volatile boolean shuttingDown;
+ private final AutoReloadRunnable reloadRunner;
@Inject
public AutoReloadConfigDecorator(
- SitePaths site,
- Destination.Factory destinationFactory,
- Provider<ReplicationQueue> replicationQueue,
- @PluginData Path pluginDataDir,
@PluginName String pluginName,
- WorkQueue workQueue)
- throws ConfigInvalidException, IOException {
- this.site = site;
- this.destinationFactory = destinationFactory;
- this.pluginDataDir = pluginDataDir;
- this.currentConfig = loadConfig();
- this.currentConfigTs = getLastModified(currentConfig);
- this.replicationQueue = replicationQueue;
+ WorkQueue workQueue,
+ ReplicationFileBasedConfig replicationConfig,
+ AutoReloadRunnable reloadRunner,
+ EventBus eventBus) {
+ this.currentConfig = replicationConfig;
this.autoReloadExecutor = workQueue.createQueue(1, pluginName + "_auto-reload-config");
- }
-
- private static long getLastModified(ReplicationFileBasedConfig cfg) {
- return FileUtil.lastModified(cfg.getCfgPath());
- }
-
- private ReplicationFileBasedConfig loadConfig() throws ConfigInvalidException, IOException {
- return new ReplicationFileBasedConfig(site, destinationFactory, pluginDataDir);
- }
-
- private synchronized boolean isAutoReload() {
- return currentConfig.getConfig().getBoolean("gerrit", "autoReload", false);
- }
-
- @Override
- public synchronized List<Destination> getDestinations(FilterType filterType) {
- return currentConfig.getDestinations(filterType);
- }
-
- @Override
- public synchronized Multimap<Destination, URIish> getURIs(
- Optional<String> remoteName, Project.NameKey projectName, FilterType filterType) {
- return currentConfig.getURIs(remoteName, projectName, filterType);
- }
-
- private synchronized void reloadIfNeeded() {
- reload(false);
+ this.reloadRunner = reloadRunner;
+ eventBus.register(this);
}
@VisibleForTesting
- public void forceReload() {
- reload(true);
- }
-
- private void reload(boolean force) {
- if (force || isAutoReload()) {
- ReplicationQueue queue = replicationQueue.get();
-
- long lastModified = getLastModified(currentConfig);
- try {
- if (force
- || (!shuttingDown
- && lastModified > currentConfigTs
- && lastModified > lastFailedConfigTs
- && queue.isRunning()
- && !queue.isReplaying())) {
- queue.stop();
- currentConfig = loadConfig();
- currentConfigTs = lastModified;
- lastFailedConfigTs = 0;
- logger.atInfo().log(
- "Configuration reloaded: %d destinations",
- currentConfig.getDestinations(FilterType.ALL).size());
- }
- } catch (Exception e) {
- logger.atSevere().withCause(e).log(
- "Cannot reload replication configuration: keeping existing settings");
- lastFailedConfigTs = lastModified;
- return;
- } finally {
- queue.start();
- }
- }
+ public void reload() {
+ reloadRunner.reload();
}
@Override
@@ -153,45 +71,36 @@
}
@Override
- public synchronized boolean isEmpty() {
- return currentConfig.isEmpty();
- }
-
- @Override
public Path getEventsDirectory() {
return currentConfig.getEventsDirectory();
}
- /* shutdown() cannot be set as a synchronized method because
- * it may need to wait for pending events to complete;
- * e.g. when enabling the drain of replication events before
- * shutdown.
- *
- * As a rule of thumb for synchronized methods, because they
- * implicitly define a critical section and associated lock,
- * they should never hold waiting for another resource, otherwise
- * the risk of deadlock is very high.
- *
- * See more background about deadlocks, what they are and how to
- * prevent them at: https://en.wikipedia.org/wiki/Deadlock
- */
@Override
- public int shutdown() {
- this.shuttingDown = true;
- if (autoReloadRunnable != null) {
- autoReloadRunnable.cancel(false);
- autoReloadRunnable = null;
- }
- return currentConfig.shutdown();
+ public synchronized void start() {
+ autoReloadRunnable =
+ autoReloadExecutor.scheduleAtFixedRate(
+ reloadRunner, RELOAD_DELAY, RELOAD_INTERVAL, TimeUnit.SECONDS);
}
@Override
- public synchronized void startup(WorkQueue workQueue) {
- shuttingDown = false;
- currentConfig.startup(workQueue);
- autoReloadRunnable =
- autoReloadExecutor.scheduleAtFixedRate(
- this::reloadIfNeeded, RELOAD_DELAY, RELOAD_INTERVAL, TimeUnit.SECONDS);
+ public synchronized void stop() {
+ if (autoReloadRunnable != null) {
+ if (!autoReloadRunnable.cancel(true)) {
+ throw new IllegalStateException(
+ "Unable to cancel replication reload task: cannot guarantee orderly shutdown");
+ }
+ autoReloadRunnable = null;
+ }
+ }
+
+ @Override
+ public String getVersion() {
+ return currentConfig.getVersion();
+ }
+
+ @Subscribe
+ public void onReload(ReplicationFileBasedConfig newConfig) {
+ currentConfig = newConfig;
}
@Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnable.java b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnable.java
new file mode 100644
index 0000000..c5009d3
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadRunnable.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2019 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.eventbus.EventBus;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.annotations.PluginData;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.nio.file.Path;
+
+public class AutoReloadRunnable implements Runnable {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final SitePaths site;
+ private final Path pluginDataDir;
+ private final EventBus eventBus;
+ private final Provider<ReplicationQueue> replicationQueue;
+
+ private ReplicationFileBasedConfig loadedConfig;
+ private String loadedConfigVersion;
+ private String lastFailedConfigVersion;
+
+ @Inject
+ public AutoReloadRunnable(
+ ReplicationFileBasedConfig config,
+ SitePaths site,
+ @PluginData Path pluginDataDir,
+ EventBus eventBus,
+ Provider<ReplicationQueue> replicationQueue) {
+ this.loadedConfig = config;
+ this.loadedConfigVersion = config.getVersion();
+ this.lastFailedConfigVersion = "";
+ this.site = site;
+ this.pluginDataDir = pluginDataDir;
+ this.eventBus = eventBus;
+ this.replicationQueue = replicationQueue;
+ }
+
+ @Override
+ public synchronized void run() {
+ String pendingConfigVersion = loadedConfig.getVersion();
+ ReplicationQueue queue = replicationQueue.get();
+ if (pendingConfigVersion.equals(loadedConfigVersion)
+ || pendingConfigVersion.equals(lastFailedConfigVersion)
+ || !queue.isRunning()
+ || queue.isReplaying()) {
+ return;
+ }
+
+ reload();
+ }
+
+ synchronized void reload() {
+ String pendingConfigVersion = loadedConfig.getVersion();
+ try {
+ loadedConfig = new ReplicationFileBasedConfig(site, pluginDataDir);
+ loadedConfigVersion = loadedConfig.getVersion();
+ lastFailedConfigVersion = "";
+ eventBus.post(loadedConfig);
+ } catch (Exception e) {
+ logger.atSevere().withCause(e).log(
+ "Cannot reload replication configuration: keeping existing settings");
+ lastFailedConfigVersion = pendingConfigVersion;
+ }
+ }
+}
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 a8dede3..9f07d76 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/CreateProjectTask.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/CreateProjectTask.java
@@ -31,7 +31,7 @@
}
private final RemoteConfig config;
- private final ReplicationConfig replicationConfig;
+ private final DestinationsCollection destinations;
private final DynamicItem<AdminApiFactory> adminApiFactory;
private final Project.NameKey project;
private final String head;
@@ -39,21 +39,20 @@
@Inject
CreateProjectTask(
RemoteConfig config,
- ReplicationConfig replicationConfig,
+ DestinationsCollection destinations,
DynamicItem<AdminApiFactory> adminApiFactory,
@Assisted Project.NameKey project,
@Assisted String head) {
this.config = config;
- this.replicationConfig = replicationConfig;
+ this.destinations = destinations;
this.adminApiFactory = adminApiFactory;
this.project = project;
this.head = head;
}
public boolean create() {
- return replicationConfig
- .getURIs(Optional.of(config.getName()), project, FilterType.PROJECT_CREATION).values()
- .stream()
+ return destinations.getURIs(Optional.of(config.getName()), project, FilterType.PROJECT_CREATION)
+ .values().stream()
.map(u -> createProject(u, project, head))
.reduce(true, (a, b) -> a && b);
}
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 679776f..996cb2a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
@@ -30,7 +30,7 @@
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.BranchNameKey;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
@@ -731,6 +731,10 @@
return config.getDelay() * 1000;
}
+ int getSlowLatencyThreshold() {
+ return config.getSlowLatencyThreshold();
+ }
+
private static boolean matches(URIish uri, String urlMatch) {
if (urlMatch == null || urlMatch.equals("") || urlMatch.equals("*")) {
return true;
@@ -750,7 +754,7 @@
ReplicationScheduledEvent event =
new ReplicationScheduledEvent(project.get(), ref, targetNode);
try {
- eventDispatcher.get().postEvent(new Branch.NameKey(project, ref), event);
+ eventDispatcher.get().postEvent(BranchNameKey.create(project, ref), event);
} catch (PermissionBackendException e) {
repLog.error("error posting event", e);
}
@@ -764,7 +768,7 @@
RefReplicatedEvent event =
new RefReplicatedEvent(project.get(), ref, targetNode, RefPushResult.FAILED, status);
try {
- eventDispatcher.get().postEvent(new Branch.NameKey(project, ref), event);
+ eventDispatcher.get().postEvent(BranchNameKey.create(project, ref), event);
} catch (PermissionBackendException e) {
repLog.error("error posting event", e);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java
index f688cfc..815b1b7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java
@@ -16,6 +16,8 @@
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.server.config.ConfigUtil;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.transport.RemoteConfig;
@@ -23,6 +25,7 @@
static final int DEFAULT_REPLICATION_DELAY = 15;
static final int DEFAULT_RESCHEDULE_DELAY = 3;
static final int DEFAULT_DRAIN_QUEUE_ATTEMPTS = 0;
+ private static final int DEFAULT_SLOW_LATENCY_THRESHOLD_SECS = 900;
private final int delay;
private final int rescheduleDelay;
@@ -41,6 +44,7 @@
private final ImmutableList<String> authGroupNames;
private final RemoteConfig remoteConfig;
private final int maxRetries;
+ private final int slowLatencyThreshold;
protected DestinationConfiguration(RemoteConfig remoteConfig, Config cfg) {
this.remoteConfig = remoteConfig;
@@ -67,6 +71,16 @@
maxRetries =
getInt(
remoteConfig, cfg, "replicationMaxRetries", cfg.getInt("replication", "maxRetries", 0));
+
+ slowLatencyThreshold =
+ (int)
+ ConfigUtil.getTimeUnit(
+ cfg,
+ "remote",
+ remoteConfig.getName(),
+ "slowLatencyThreshold",
+ DEFAULT_SLOW_LATENCY_THRESHOLD_SECS,
+ TimeUnit.SECONDS);
}
public int getDelay() {
@@ -140,4 +154,8 @@
private static int getInt(RemoteConfig rc, Config cfg, String name, int defValue) {
return cfg.getInt("remote", rc.getName(), name, defValue);
}
+
+ public int getSlowLatencyThreshold() {
+ return slowLatencyThreshold;
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationsCollection.java b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationsCollection.java
new file mode 100644
index 0000000..a1f0095
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationsCollection.java
@@ -0,0 +1,349 @@
+// Copyright (C) 2019 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.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.ReplicationQueue.repLog;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.SetMultimap;
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.replication.Destination.Factory;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+
+@Singleton
+public class DestinationsCollection implements ReplicationDestinations {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final Factory destinationFactory;
+ private final Provider<ReplicationQueue> replicationQueue;
+ private volatile ReplicationFileBasedConfig replicationConfig;
+ private volatile List<Destination> destinations;
+ private boolean shuttingDown;
+
+ public static class EventQueueNotEmptyException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public EventQueueNotEmptyException(String errorMessage) {
+ super(errorMessage);
+ }
+ }
+
+ @Inject
+ public DestinationsCollection(
+ Destination.Factory destinationFactory,
+ Provider<ReplicationQueue> replicationQueue,
+ ReplicationFileBasedConfig replicationConfig,
+ EventBus eventBus)
+ throws ConfigInvalidException {
+ this.destinationFactory = destinationFactory;
+ this.replicationQueue = replicationQueue;
+ this.replicationConfig = replicationConfig;
+ this.destinations = allDestinations(destinationFactory);
+ eventBus.register(this);
+ }
+
+ @Override
+ public Multimap<Destination, URIish> getURIs(
+ Optional<String> remoteName, Project.NameKey projectName, FilterType filterType) {
+ if (getAll(filterType).isEmpty()) {
+ return ImmutableMultimap.of();
+ }
+
+ SetMultimap<Destination, URIish> uris = HashMultimap.create();
+ for (Destination config : getAll(filterType)) {
+ if (!config.wouldPushProject(projectName)) {
+ continue;
+ }
+
+ if (remoteName.isPresent() && !config.getRemoteConfigName().equals(remoteName.get())) {
+ continue;
+ }
+
+ boolean adminURLUsed = false;
+
+ for (String url : config.getAdminUrls()) {
+ if (Strings.isNullOrEmpty(url)) {
+ continue;
+ }
+
+ URIish uri;
+ try {
+ uri = new URIish(url);
+ } catch (URISyntaxException e) {
+ repLog.warn("adminURL '{}' is invalid: {}", url, e.getMessage());
+ continue;
+ }
+
+ if (!isGerrit(uri) && !isGerritHttp(uri)) {
+ String path =
+ replaceName(uri.getPath(), projectName.get(), config.isSingleProjectMatch());
+ if (path == null) {
+ repLog.warn("adminURL {} does not contain ${name}", uri);
+ continue;
+ }
+
+ uri = uri.setPath(path);
+ if (!isSSH(uri)) {
+ repLog.warn("adminURL '{}' is invalid: only SSH and HTTP are supported", uri);
+ continue;
+ }
+ }
+ uris.put(config, uri);
+ adminURLUsed = true;
+ }
+
+ if (!adminURLUsed) {
+ for (URIish uri : config.getURIs(projectName, "*")) {
+ uris.put(config, uri);
+ }
+ }
+ }
+ return uris;
+ }
+
+ @Override
+ public synchronized List<Destination> getAll(FilterType filterType) {
+ Predicate<? super Destination> filter;
+ switch (filterType) {
+ case PROJECT_CREATION:
+ filter = dest -> dest.isCreateMissingRepos();
+ break;
+ case PROJECT_DELETION:
+ filter = dest -> dest.isReplicateProjectDeletions();
+ break;
+ case ALL:
+ default:
+ filter = dest -> true;
+ break;
+ }
+ return destinations.stream().filter(Objects::nonNull).filter(filter).collect(toList());
+ }
+
+ @Override
+ public synchronized boolean isEmpty() {
+ return destinations.isEmpty();
+ }
+
+ @Override
+ public synchronized void startup(WorkQueue workQueue) {
+ shuttingDown = false;
+ for (Destination cfg : destinations) {
+ cfg.start(workQueue);
+ }
+ }
+
+ /* shutdown() cannot be set as a synchronized method because
+ * it may need to wait for pending events to complete;
+ * e.g. when enabling the drain of replication events before
+ * shutdown.
+ *
+ * As a rule of thumb for synchronized methods, because they
+ * implicitly define a critical section and associated lock,
+ * they should never hold waiting for another resource, otherwise
+ * the risk of deadlock is very high.
+ *
+ * See more background about deadlocks, what they are and how to
+ * prevent them at: https://en.wikipedia.org/wiki/Deadlock
+ */
+ @Override
+ public int shutdown() {
+ synchronized (this) {
+ shuttingDown = true;
+ }
+
+ int discarded = 0;
+ for (Destination cfg : destinations) {
+ try {
+ drainReplicationEvents(cfg);
+ } catch (EventQueueNotEmptyException e) {
+ logger.atWarning().log("Event queue not empty: %s", e.getMessage());
+ } finally {
+ discarded += cfg.shutdown();
+ }
+ }
+ return discarded;
+ }
+
+ void drainReplicationEvents(Destination destination) throws EventQueueNotEmptyException {
+ int drainQueueAttempts = destination.getDrainQueueAttempts();
+ if (drainQueueAttempts == 0) {
+ return;
+ }
+ int pending = destination.getQueueInfo().pending.size();
+ int inFlight = destination.getQueueInfo().inFlight.size();
+ while ((inFlight > 0 || pending > 0) && drainQueueAttempts > 0) {
+ try {
+ logger.atInfo().log(
+ "Draining replication events, postpone shutdown. Events left: inFlight %d, pending %d",
+ inFlight, pending);
+ Thread.sleep(destination.getReplicationDelaySeconds());
+ } catch (InterruptedException ie) {
+ logger.atWarning().withCause(ie).log(
+ "Wait for replication events to drain has been interrupted");
+ }
+ pending = destination.getQueueInfo().pending.size();
+ inFlight = destination.getQueueInfo().inFlight.size();
+ drainQueueAttempts--;
+ }
+ if (pending > 0 || inFlight > 0) {
+ throw new EventQueueNotEmptyException(
+ String.format("Pending: %d - InFlight: %d", pending, inFlight));
+ }
+ }
+
+ @Subscribe
+ public synchronized void onReload(ReplicationFileBasedConfig newConfig)
+ throws ConfigInvalidException {
+ if (shuttingDown) {
+ logger.atWarning().log("Shutting down: configuration reload ignored");
+ return;
+ }
+
+ try {
+ replicationQueue.get().stop();
+ replicationConfig = newConfig;
+ destinations = allDestinations(destinationFactory);
+ logger.atInfo().log("Configuration reloaded: %d destinations", getAll(FilterType.ALL).size());
+ } finally {
+ replicationQueue.get().start();
+ }
+ }
+
+ private List<Destination> allDestinations(Destination.Factory destinationFactory)
+ throws ConfigInvalidException {
+ if (!replicationConfig.getConfig().getFile().exists()) {
+ logger.atWarning().log(
+ "Config file %s does not exist; not replicating",
+ replicationConfig.getConfig().getFile());
+ return Collections.emptyList();
+ }
+ if (replicationConfig.getConfig().getFile().length() == 0) {
+ logger.atInfo().log(
+ "Config file %s is empty; not replicating", replicationConfig.getConfig().getFile());
+ return Collections.emptyList();
+ }
+
+ try {
+ replicationConfig.getConfig().load();
+ } catch (ConfigInvalidException e) {
+ throw new ConfigInvalidException(
+ String.format(
+ "Config file %s is invalid: %s",
+ replicationConfig.getConfig().getFile(), e.getMessage()),
+ e);
+ } catch (IOException e) {
+ throw new ConfigInvalidException(
+ String.format(
+ "Cannot read %s: %s", replicationConfig.getConfig().getFile(), e.getMessage()),
+ e);
+ }
+
+ boolean defaultForceUpdate =
+ replicationConfig.getConfig().getBoolean("gerrit", "defaultForceUpdate", false);
+
+ ImmutableList.Builder<Destination> dest = ImmutableList.builder();
+ for (RemoteConfig c : allRemotes(replicationConfig.getConfig())) {
+ if (c.getURIs().isEmpty()) {
+ continue;
+ }
+
+ // If destination for push is not set assume equal to source.
+ for (RefSpec ref : c.getPushRefSpecs()) {
+ if (ref.getDestination() == null) {
+ ref.setDestination(ref.getSource());
+ }
+ }
+
+ if (c.getPushRefSpecs().isEmpty()) {
+ c.addPushRefSpec(
+ new RefSpec()
+ .setSourceDestination("refs/*", "refs/*")
+ .setForceUpdate(defaultForceUpdate));
+ }
+
+ Destination destination =
+ destinationFactory.create(new DestinationConfiguration(c, replicationConfig.getConfig()));
+
+ if (!destination.isSingleProjectMatch()) {
+ for (URIish u : c.getURIs()) {
+ if (u.getPath() == null || !u.getPath().contains("${name}")) {
+ throw new ConfigInvalidException(
+ String.format(
+ "remote.%s.url \"%s\" lacks ${name} placeholder in %s",
+ c.getName(), u, replicationConfig.getConfig().getFile()));
+ }
+ }
+ }
+
+ dest.add(destination);
+ }
+ return dest.build();
+ }
+
+ private static List<RemoteConfig> allRemotes(FileBasedConfig cfg) throws ConfigInvalidException {
+ Set<String> names = cfg.getSubsections("remote");
+ List<RemoteConfig> result = Lists.newArrayListWithCapacity(names.size());
+ for (String name : names) {
+ try {
+ result.add(new RemoteConfig(cfg, name));
+ } catch (URISyntaxException e) {
+ throw new ConfigInvalidException(
+ String.format("remote %s has invalid URL in %s", name, cfg.getFile()));
+ }
+ }
+ return result;
+ }
+
+ static String replaceName(String in, String name, boolean keyIsOptional) {
+ String key = "${name}";
+ int n = in.indexOf(key);
+ if (0 <= n) {
+ return in.substring(0, n) + name + in.substring(n + key.length());
+ }
+ if (keyIsOptional) {
+ return in;
+ }
+ return null;
+ }
+}
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 fa17dce..d337883 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java
@@ -40,11 +40,11 @@
@Option(name = "--json", usage = "output in json format")
private boolean json;
- @Inject private ReplicationConfig config;
+ @Inject private ReplicationDestinations destinations;
@Override
protected void run() {
- for (Destination d : config.getDestinations(FilterType.ALL)) {
+ for (Destination d : destinations.getAll(FilterType.ALL)) {
if (matches(d.getRemoteConfigName())) {
printRemote(d);
}
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 56cecfe..af34555 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
@@ -16,6 +16,7 @@
import static com.googlesource.gerrit.plugins.replication.ReplicationQueue.repLog;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toMap;
import com.google.common.base.Throwables;
@@ -329,14 +330,19 @@
}
repLog.info("Replication to {} started...", uri);
- Timer1.Context context = metrics.start(config.getName());
+ Timer1.Context<String> destinationContext = metrics.start(config.getName());
try {
- long startedAt = context.getStartTime();
+ long startedAt = destinationContext.getStartTime();
long delay = NANOSECONDS.toMillis(startedAt - createdAt);
metrics.record(config.getName(), delay, retryCount);
git = gitManager.openRepository(projectName);
runImpl();
- long elapsed = NANOSECONDS.toMillis(context.stop());
+ long elapsed = NANOSECONDS.toMillis(destinationContext.stop());
+
+ if (elapsed > SECONDS.toMillis(pool.getSlowLatencyThreshold())) {
+ metrics.recordSlowProjectReplication(
+ config.getName(), projectName.get(), pool.getSlowLatencyThreshold(), elapsed);
+ }
repLog.info(
"Replication to {} completed in {}ms, {}ms delay, {} retries",
uri,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEvent.java b/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEvent.java
index fccdb7b..511a8fc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEvent.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEvent.java
@@ -17,6 +17,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.events.RefEvent;
import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult;
+import java.util.Objects;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
@@ -45,11 +46,40 @@
@Override
public Project.NameKey getProjectNameKey() {
- return new Project.NameKey(project);
+ return Project.nameKey(project);
}
@Override
public String getRefName() {
return ref;
}
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof RefReplicatedEvent)) {
+ return false;
+ }
+ RefReplicatedEvent event = (RefReplicatedEvent) other;
+ if (!Objects.equals(event.project, this.project)) {
+ return false;
+ }
+ if (!Objects.equals(event.ref, this.ref)) {
+ return false;
+ }
+ if (!Objects.equals(event.targetNode, this.targetNode)) {
+ return false;
+ }
+ if (!Objects.equals(event.status, this.status)) {
+ return false;
+ }
+ if (!Objects.equals(event.refStatus, this.refStatus)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEvent.java b/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEvent.java
index 4789a96..7acd3d1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEvent.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEvent.java
@@ -16,6 +16,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.events.RefEvent;
+import java.util.Objects;
public class RefReplicationDoneEvent extends RefEvent {
public static final String TYPE = "ref-replication-done";
@@ -33,11 +34,35 @@
@Override
public Project.NameKey getProjectNameKey() {
- return new Project.NameKey(project);
+ return Project.nameKey(project);
}
@Override
public String getRefName() {
return ref;
}
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof RefReplicationDoneEvent)) {
+ return false;
+ }
+
+ RefReplicationDoneEvent event = (RefReplicationDoneEvent) other;
+ if (!Objects.equals(event.project, this.project)) {
+ return false;
+ }
+ if (!Objects.equals(event.ref, this.ref)) {
+ return false;
+ }
+ if (event.nodesCount != this.nodesCount) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java
index 929c538..b981bc8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java
@@ -11,44 +11,63 @@
// 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.collect.Multimap;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.git.WorkQueue;
import java.nio.file.Path;
-import java.util.List;
-import java.util.Optional;
-import org.eclipse.jgit.transport.URIish;
+/** Configuration of all the replication end points. */
public interface ReplicationConfig {
+ /** Filter for accessing replication projects. */
enum FilterType {
PROJECT_CREATION,
PROJECT_DELETION,
ALL
}
- List<Destination> getDestinations(FilterType filterType);
-
- Multimap<Destination, URIish> getURIs(
- Optional<String> remoteName, Project.NameKey projectName, FilterType filterType);
-
+ /**
+ * Returns current replication configuration of whether to replicate or not all the projects when
+ * the plugin starts.
+ *
+ * @return true if replication at plugin start, false otherwise.
+ */
boolean isReplicateAllOnPluginStart();
+ /**
+ * Returns the default behaviour of the replication plugin when pushing to remote replication
+ * ends. Even though the property name has the 'update' suffix, it actually refers to Git push
+ * operation and not to a Git update.
+ *
+ * @return true if forced push is the default, false otherwise.
+ */
boolean isDefaultForceUpdate();
+ /**
+ * Returns the maximum number of ref-specs to log into the replication_log whenever a push
+ * operation is completed against a replication end.
+ *
+ * @return maximum number of refs to log, zero if unlimited.
+ */
int getMaxRefsToLog();
- boolean isEmpty();
-
+ /**
+ * Configured location where the replication events are stored on the filesystem for being resumed
+ * and kept across restarts.
+ *
+ * @return path to store persisted events.
+ */
Path getEventsDirectory();
- int shutdown();
-
- void startup(WorkQueue workQueue);
-
int getSshConnectionTimeout();
int getSshCommandTimeout();
+
+ /**
+ * Current logical version string of the current configuration loaded in memory, depending on the
+ * actual implementation of the configuration on the persistent storage.
+ *
+ * @return current logical version number.
+ */
+ String getVersion();
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationDestinations.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationDestinations.java
new file mode 100644
index 0000000..1a6b77f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationDestinations.java
@@ -0,0 +1,63 @@
+// Copyright (C) 2019 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.collect.Multimap;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.WorkQueue;
+import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jgit.transport.URIish;
+
+/** Git destinations currently active for replication. */
+public interface ReplicationDestinations {
+
+ /**
+ * Return all the URIs associated to a project and a filter criteria.
+ *
+ * @param remoteName name of the replication end or empty if selecting all ends.
+ * @param projectName name of the project
+ * @param filterType type of filter criteria for selecting projects
+ * @return the multi-map of destinations and the associated replication URIs
+ */
+ Multimap<Destination, URIish> getURIs(
+ Optional<String> remoteName, Project.NameKey projectName, FilterType filterType);
+
+ /**
+ * List of currently active replication destinations.
+ *
+ * @param filterType type project filtering
+ * @return the list of active destinations
+ */
+ List<Destination> getAll(FilterType filterType);
+
+ /** @return true if there are no destinations, false otherwise. */
+ boolean isEmpty();
+
+ /**
+ * Start replicating to all destinations.
+ *
+ * @param workQueue execution queue for scheduling the replication events.
+ */
+ void startup(WorkQueue workQueue);
+
+ /**
+ * Stop the replication to all destinations.
+ *
+ * @return number of events cancelled during shutdown.
+ */
+ int shutdown();
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
index 6476412..83f941c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java
@@ -13,51 +13,19 @@
// limitations under the License.
package com.googlesource.gerrit.plugins.replication;
-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.ReplicationQueue.repLog;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static java.util.stream.Collectors.toList;
-
import com.google.common.base.Strings;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.SetMultimap;
-import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.annotations.PluginData;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.WorkQueue;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import java.io.IOException;
-import java.net.URISyntaxException;
import java.nio.file.Path;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Predicate;
-import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.transport.RefSpec;
-import org.eclipse.jgit.transport.RemoteConfig;
-import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;
@Singleton
public class ReplicationFileBasedConfig implements ReplicationConfig {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final int DEFAULT_SSH_CONNECTION_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes
- private List<Destination> destinations;
private final SitePaths site;
private Path cfgPath;
private boolean replicateAllOnPluginStart;
@@ -69,176 +37,16 @@
private final Path pluginDataDir;
@Inject
- public ReplicationFileBasedConfig(
- SitePaths site, Destination.Factory destinationFactory, @PluginData Path pluginDataDir)
- throws ConfigInvalidException, IOException {
+ public ReplicationFileBasedConfig(SitePaths site, @PluginData Path pluginDataDir) {
this.site = site;
this.cfgPath = site.etc_dir.resolve("replication.config");
this.config = new FileBasedConfig(cfgPath.toFile(), FS.DETECTED);
- this.destinations = allDestinations(destinationFactory);
+ this.replicateAllOnPluginStart = config.getBoolean("gerrit", "replicateOnStartup", false);
+ this.defaultForceUpdate = config.getBoolean("gerrit", "defaultForceUpdate", false);
+ this.maxRefsToLog = config.getInt("gerrit", "maxRefsToLog", 0);
this.pluginDataDir = pluginDataDir;
}
- /*
- * (non-Javadoc)
- * @see
- * com.googlesource.gerrit.plugins.replication.ReplicationConfig#getDestinations
- * (com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType)
- */
- @Override
- public List<Destination> getDestinations(FilterType filterType) {
- Predicate<? super Destination> filter;
- switch (filterType) {
- case PROJECT_CREATION:
- filter = dest -> dest.isCreateMissingRepos();
- break;
- case PROJECT_DELETION:
- filter = dest -> dest.isReplicateProjectDeletions();
- break;
- case ALL:
- default:
- filter = dest -> true;
- break;
- }
- return destinations.stream().filter(Objects::nonNull).filter(filter).collect(toList());
- }
-
- private List<Destination> allDestinations(Destination.Factory destinationFactory)
- throws ConfigInvalidException, IOException {
- if (!config.getFile().exists()) {
- logger.atWarning().log("Config file %s does not exist; not replicating", config.getFile());
- return Collections.emptyList();
- }
- if (config.getFile().length() == 0) {
- logger.atInfo().log("Config file %s is empty; not replicating", config.getFile());
- return Collections.emptyList();
- }
-
- try {
- config.load();
- } catch (ConfigInvalidException e) {
- throw new ConfigInvalidException(
- String.format("Config file %s is invalid: %s", config.getFile(), e.getMessage()), e);
- } catch (IOException e) {
- throw new IOException(
- String.format("Cannot read %s: %s", config.getFile(), e.getMessage()), e);
- }
-
- replicateAllOnPluginStart = config.getBoolean("gerrit", "replicateOnStartup", false);
-
- defaultForceUpdate = config.getBoolean("gerrit", "defaultForceUpdate", false);
-
- maxRefsToLog = config.getInt("gerrit", "maxRefsToLog", 0);
-
- sshCommandTimeout =
- (int) ConfigUtil.getTimeUnit(config, "gerrit", null, "sshCommandTimeout", 0, SECONDS);
- sshConnectionTimeout =
- (int)
- ConfigUtil.getTimeUnit(
- config,
- "gerrit",
- null,
- "sshConnectionTimeout",
- DEFAULT_SSH_CONNECTION_TIMEOUT_MS,
- MILLISECONDS);
-
- ImmutableList.Builder<Destination> dest = ImmutableList.builder();
- for (RemoteConfig c : allRemotes(config)) {
- if (c.getURIs().isEmpty()) {
- continue;
- }
-
- // If destination for push is not set assume equal to source.
- for (RefSpec ref : c.getPushRefSpecs()) {
- if (ref.getDestination() == null) {
- ref.setDestination(ref.getSource());
- }
- }
-
- if (c.getPushRefSpecs().isEmpty()) {
- c.addPushRefSpec(
- new RefSpec()
- .setSourceDestination("refs/*", "refs/*")
- .setForceUpdate(defaultForceUpdate));
- }
-
- Destination destination = destinationFactory.create(new DestinationConfiguration(c, config));
-
- if (!destination.isSingleProjectMatch()) {
- for (URIish u : c.getURIs()) {
- if (u.getPath() == null || !u.getPath().contains("${name}")) {
- throw new ConfigInvalidException(
- String.format(
- "remote.%s.url \"%s\" lacks ${name} placeholder in %s",
- c.getName(), u, config.getFile()));
- }
- }
- }
-
- dest.add(destination);
- }
- return dest.build();
- }
-
- @Override
- public Multimap<Destination, URIish> getURIs(
- Optional<String> remoteName, Project.NameKey projectName, FilterType filterType) {
- if (getDestinations(filterType).isEmpty()) {
- return ImmutableMultimap.of();
- }
-
- SetMultimap<Destination, URIish> uris = HashMultimap.create();
- for (Destination config : getDestinations(filterType)) {
- if (!config.wouldPushProject(projectName)) {
- continue;
- }
-
- if (remoteName.isPresent() && !config.getRemoteConfigName().equals(remoteName.get())) {
- continue;
- }
-
- boolean adminURLUsed = false;
-
- for (String url : config.getAdminUrls()) {
- if (Strings.isNullOrEmpty(url)) {
- continue;
- }
-
- URIish uri;
- try {
- uri = new URIish(url);
- } catch (URISyntaxException e) {
- repLog.warn("adminURL '{}' is invalid: {}", url, e.getMessage());
- continue;
- }
-
- if (!isGerrit(uri) && !isGerritHttp(uri)) {
- String path =
- replaceName(uri.getPath(), projectName.get(), config.isSingleProjectMatch());
- if (path == null) {
- repLog.warn("adminURL {} does not contain ${name}", uri);
- continue;
- }
-
- uri = uri.setPath(path);
- if (!isSSH(uri)) {
- repLog.warn("adminURL '{}' is invalid: only SSH and HTTP are supported", uri);
- continue;
- }
- }
- uris.put(config, uri);
- adminURLUsed = true;
- }
-
- if (!adminURLUsed) {
- for (URIish uri : config.getURIs(projectName, "*")) {
- uris.put(config, uri);
- }
- }
- }
- return uris;
- }
-
static String replaceName(String in, String name, boolean keyIsOptional) {
String key = "${name}";
int n = in.indexOf(key);
@@ -272,28 +80,6 @@
return maxRefsToLog;
}
- private static List<RemoteConfig> allRemotes(FileBasedConfig cfg) throws ConfigInvalidException {
- Set<String> names = cfg.getSubsections("remote");
- List<RemoteConfig> result = Lists.newArrayListWithCapacity(names.size());
- for (String name : names) {
- try {
- result.add(new RemoteConfig(cfg, name));
- } catch (URISyntaxException e) {
- throw new ConfigInvalidException(
- String.format("remote %s has invalid URL in %s", name, cfg.getFile()));
- }
- }
- return result;
- }
-
- /* (non-Javadoc)
- * @see com.googlesource.gerrit.plugins.replication.ReplicationConfig#isEmpty()
- */
- @Override
- public boolean isEmpty() {
- return destinations.isEmpty();
- }
-
@Override
public Path getEventsDirectory() {
String eventsDirectory = config.getString("replication", null, "eventsDirectory");
@@ -307,67 +93,13 @@
return cfgPath;
}
- @Override
- public int shutdown() {
- int discarded = 0;
- for (Destination cfg : destinations) {
- try {
- drainReplicationEvents(cfg);
- } catch (EventQueueNotEmptyException e) {
- logger.atWarning().log("Event queue not empty: %s", e.getMessage());
- } finally {
- discarded += cfg.shutdown();
- }
- }
- return discarded;
- }
-
- void drainReplicationEvents(Destination destination) throws EventQueueNotEmptyException {
- int drainQueueAttempts = destination.getDrainQueueAttempts();
- if (drainQueueAttempts == 0) {
- return;
- }
- int pending = destination.getQueueInfo().pending.size();
- int inFlight = destination.getQueueInfo().inFlight.size();
-
- while ((inFlight > 0 || pending > 0) && drainQueueAttempts > 0) {
- try {
- logger.atInfo().log(
- "Draining replication events, postpone shutdown. Events left: inFlight %d, pending %d",
- inFlight, pending);
- Thread.sleep(destination.getReplicationDelaySeconds());
- } catch (InterruptedException ie) {
- logger.atWarning().withCause(ie).log(
- "Wait for replication events to drain has been interrupted");
- }
- pending = destination.getQueueInfo().pending.size();
- inFlight = destination.getQueueInfo().inFlight.size();
- drainQueueAttempts--;
- }
-
- if (pending > 0 || inFlight > 0) {
- throw new EventQueueNotEmptyException(
- String.format("Pending: %d - InFlight: %d", pending, inFlight));
- }
- }
-
- public static class EventQueueNotEmptyException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public EventQueueNotEmptyException(String errorMessage) {
- super(errorMessage);
- }
- }
-
FileBasedConfig getConfig() {
return config;
}
@Override
- public void startup(WorkQueue workQueue) {
- for (Destination cfg : destinations) {
- cfg.start(workQueue);
- }
+ public String getVersion() {
+ return Long.toString(config.getFile().lastModified());
}
@Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java
index afc7926..1bc17ec 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java
@@ -14,11 +14,14 @@
package com.googlesource.gerrit.plugins.replication;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram1;
+import com.google.gerrit.metrics.Histogram3;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.server.logging.PluginMetadata;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -27,10 +30,37 @@
private final Timer1<String> executionTime;
private final Histogram1<String> executionDelay;
private final Histogram1<String> executionRetries;
+ private final Histogram3<Integer, String, String> slowProjectReplicationLatency;
@Inject
- ReplicationMetrics(MetricMaker metricMaker) {
- Field<String> DEST_FIELD = Field.ofString("destination");
+ ReplicationMetrics(@PluginName String pluginName, MetricMaker metricMaker) {
+ Field<String> DEST_FIELD =
+ Field.ofString(
+ "destination",
+ (metadataBuilder, fieldValue) ->
+ metadataBuilder
+ .pluginName(pluginName)
+ .addPluginMetadata(PluginMetadata.create("destination", fieldValue)))
+ .build();
+
+ Field<String> PROJECT_FIELD =
+ Field.ofString(
+ "project",
+ (metadataBuilder, fieldValue) ->
+ metadataBuilder
+ .pluginName(pluginName)
+ .addPluginMetadata(PluginMetadata.create("project", fieldValue)))
+ .build();
+
+ Field<Integer> SLOW_THRESHOLD_FIELD =
+ Field.ofInteger(
+ "slow_threshold",
+ (metadataBuilder, fieldValue) ->
+ metadataBuilder
+ .pluginName(pluginName)
+ .addPluginMetadata(
+ PluginMetadata.create("slow_threshold", fieldValue.toString())))
+ .build();
executionTime =
metricMaker.newTimer(
@@ -55,6 +85,17 @@
.setCumulative()
.setUnit("retries"),
DEST_FIELD);
+
+ slowProjectReplicationLatency =
+ metricMaker.newHistogram(
+ "latency_slower_than_threshold" + "",
+ new Description(
+ "latency for project to destination, where latency was slower than threshold")
+ .setCumulative()
+ .setUnit(Description.Units.MILLISECONDS),
+ SLOW_THRESHOLD_FIELD,
+ PROJECT_FIELD,
+ DEST_FIELD);
}
/**
@@ -63,7 +104,7 @@
* @param name the destination name.
* @return the timer context.
*/
- Timer1.Context start(String name) {
+ Timer1.Context<String> start(String name) {
return executionTime.start(name);
}
@@ -78,4 +119,17 @@
executionDelay.record(name, delay);
executionRetries.record(name, retries);
}
+
+ /**
+ * Record replication latency for project to destination, where latency was slower than threshold
+ *
+ * @param destinationName the destination name.
+ * @param projectName the project name.
+ * @param slowThreshold replication initialDelay in milliseconds.
+ * @param latency number of retries.
+ */
+ void recordSlowProjectReplication(
+ String destinationName, String projectName, Integer slowThreshold, long latency) {
+ slowProjectReplicationLatency.record(slowThreshold, destinationName, projectName, latency);
+ }
}
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 835d068..be6e7f6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java
@@ -16,6 +16,7 @@
import static com.googlesource.gerrit.plugins.replication.StartReplicationCapability.START_REPLICATION;
+import com.google.common.eventbus.EventBus;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
@@ -23,14 +24,30 @@
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;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+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 Path cfgPath;
+
+ @Inject
+ public ReplicationModule(SitePaths site) {
+ cfgPath = site.etc_dir.resolve("replication.config");
+ }
+
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(Destination.Factory.class));
@@ -57,7 +74,18 @@
install(new FactoryModuleBuilder().build(PushAll.Factory.class));
- bind(ReplicationConfig.class).to(AutoReloadConfigDecorator.class);
+ bind(EventBus.class).in(Scopes.SINGLETON);
+ bind(ReplicationDestinations.class).to(DestinationsCollection.class);
+
+ if (getReplicationConfig().getBoolean("gerrit", "autoReload", false)) {
+ bind(ReplicationConfig.class).to(AutoReloadConfigDecorator.class);
+ bind(LifecycleListener.class)
+ .annotatedWith(UniqueAnnotations.create())
+ .to(AutoReloadConfigDecorator.class);
+ } else {
+ bind(ReplicationConfig.class).to(ReplicationFileBasedConfig.class);
+ }
+
DynamicSet.setOf(binder(), ReplicationStateListener.class);
DynamicSet.bind(binder(), ReplicationStateListener.class).to(ReplicationStateLogger.class);
@@ -68,4 +96,15 @@
bind(TransportFactory.class).to(TransportFactoryImpl.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;
+ }
}
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 8030d28..3e17167 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
@@ -26,6 +26,7 @@
import com.google.gerrit.server.events.EventDispatcher;
import com.google.gerrit.server.git.WorkQueue;
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;
@@ -50,7 +51,7 @@
private final WorkQueue workQueue;
private final DynamicItem<EventDispatcher> dispatcher;
- private final ReplicationConfig config;
+ private final Provider<ReplicationDestinations> destinations; // For Guice circular dependency
private final ReplicationTasksStorage replicationTasksStorage;
private volatile boolean running;
private volatile boolean replaying;
@@ -59,13 +60,13 @@
@Inject
ReplicationQueue(
WorkQueue wq,
- ReplicationConfig rc,
+ Provider<ReplicationDestinations> rd,
DynamicItem<EventDispatcher> dis,
ReplicationStateListeners sl,
ReplicationTasksStorage rts) {
workQueue = wq;
dispatcher = dis;
- config = rc;
+ destinations = rd;
stateLog = sl;
replicationTasksStorage = rts;
beforeStartupEventsQueue = Queues.newConcurrentLinkedQueue();
@@ -74,7 +75,7 @@
@Override
public void start() {
if (!running) {
- config.startup(workQueue);
+ destinations.get().startup(workQueue);
running = true;
firePendingEvents();
fireBeforeStartupEvents();
@@ -84,7 +85,7 @@
@Override
public void stop() {
running = false;
- int discarded = config.shutdown();
+ int discarded = destinations.get().shutdown();
if (discarded > 0) {
repLog.warn("Canceled {} replication events during shutdown", discarded);
}
@@ -110,7 +111,7 @@
return;
}
- for (Destination cfg : config.getDestinations(FilterType.ALL)) {
+ for (Destination cfg : destinations.get().getAll(FilterType.ALL)) {
if (cfg.wouldPushProject(project)) {
for (URIish uri : cfg.getURIs(project, urlMatch)) {
cfg.schedule(project, PushOne.ALL_REFS, uri, state, now);
@@ -137,8 +138,8 @@
return;
}
- Project.NameKey project = new Project.NameKey(projectName);
- for (Destination cfg : config.getDestinations(FilterType.ALL)) {
+ Project.NameKey project = Project.nameKey(projectName);
+ for (Destination cfg : destinations.get().getAll(FilterType.ALL)) {
if (cfg.wouldPushProject(project) && cfg.wouldPushRef(refName)) {
for (URIish uri : cfg.getURIs(project, null)) {
replicationTasksStorage.persist(
@@ -170,15 +171,15 @@
@Override
public void onProjectDeleted(ProjectDeletedListener.Event event) {
- Project.NameKey p = new Project.NameKey(event.getProjectName());
- config.getURIs(Optional.empty(), p, FilterType.PROJECT_DELETION).entries().stream()
+ Project.NameKey p = Project.nameKey(event.getProjectName());
+ destinations.get().getURIs(Optional.empty(), p, FilterType.PROJECT_DELETION).entries().stream()
.forEach(e -> e.getKey().scheduleDeleteProject(e.getValue(), p));
}
@Override
public void onHeadUpdated(HeadUpdatedListener.Event event) {
- Project.NameKey p = new Project.NameKey(event.getProjectName());
- config.getURIs(Optional.empty(), p, FilterType.ALL).entries().stream()
+ Project.NameKey p = Project.nameKey(event.getProjectName());
+ destinations.get().getURIs(Optional.empty(), p, FilterType.ALL).entries().stream()
.forEach(e -> e.getKey().scheduleUpdateHead(e.getValue(), p, event.getNewHeadName()));
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationScheduledEvent.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationScheduledEvent.java
index aa965fe..005d983 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationScheduledEvent.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationScheduledEvent.java
@@ -38,6 +38,6 @@
@Override
public Project.NameKey getProjectNameKey() {
- return new Project.NameKey(project);
+ return Project.nameKey(project);
}
}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 32bc630..d9d4bae 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -419,6 +419,15 @@
By default, replicates without matching, i.e. replicates
everything to all remotes.
+remote.NAME.slowLatencyThreshold
+: the time duration after which the replication of a project to this
+ destination will be considered "slow". A slow project replication
+ will cause additional metrics to be exposed for further investigation.
+ See [metrics.md](metrics.md) for further details.
+
+ default: 15 minutes
+
+
File `secure.config`
--------------------
diff --git a/src/main/resources/Documentation/metrics.md b/src/main/resources/Documentation/metrics.md
new file mode 100644
index 0000000..ef8b150
--- /dev/null
+++ b/src/main/resources/Documentation/metrics.md
@@ -0,0 +1,61 @@
+# Metrics
+
+Some metrics are emitted when replication occurs to a remote destination.
+The granularity of the metrics recorded is at destination level, however when a particular project replication is flagged
+as slow. This happens when the replication took longer than allowed threshold (see _remote.NAME.slowLatencyThreshold_ in [config.md](config.md))
+
+The reason only slow metrics are published, rather than all, is to contain their number, which, on a big Gerrit installation
+could potentially be considerably big.
+
+### Project level
+
+* plugins_replication_latency_slower_than_<threshold>_<destinationName>_<ProjectName> - Time spent pushing <ProjectName> to remote <destinationName> (in ms)
+
+### Destination level
+
+* plugins_replication_replication_delay_<destinationName> - Time spent waiting before pushing to remote <destinationName> (in ms)
+* plugins_replication_replication_retries_<destinationName> - Number of retries when pushing to remote <destinationName>
+* plugins_replication_replication_latency_<destinationName> - Time spent pushing to remote <destinationName> (in ms)
+
+### Example
+```
+# HELP plugins_replication_replication_delay_destination Generated from Dropwizard metric import (metric=plugins/replication/replication_delay/destination, type=com.codahale.metrics.Histogram)
+# TYPE plugins_replication_replication_delay_destination summary
+plugins_replication_replication_delay_destinationName{quantile="0.5",} 65726.0
+plugins_replication_replication_delay_destinationName{quantile="0.75",} 65726.0
+plugins_replication_replication_delay_destinationName{quantile="0.95",} 65726.0
+plugins_replication_replication_delay_destinationName{quantile="0.98",} 65726.0
+plugins_replication_replication_delay_destinationName{quantile="0.99",} 65726.0
+plugins_replication_replication_delay_destinationName{quantile="0.999",} 65726.0
+plugins_replication_replication_delay_destinationName_count 3.0
+
+# HELP plugins_replication_replication_retries_destination Generated from Dropwizard metric import (metric=plugins/replication/replication_retries/destination, type=com.codahale.metrics.Histogram)
+# TYPE plugins_replication_replication_retries_destination summary
+plugins_replication_replication_retries_destinationName{quantile="0.5",} 1.0
+plugins_replication_replication_retries_destinationName{quantile="0.75",} 1.0
+plugins_replication_replication_retries_destinationName{quantile="0.95",} 1.0
+plugins_replication_replication_retries_destinationName{quantile="0.98",} 1.0
+plugins_replication_replication_retries_destinationName{quantile="0.99",} 1.0
+plugins_replication_replication_retries_destinationName{quantile="0.999",} 1.0
+plugins_replication_replication_retries_destinationName_count 3.0
+
+# HELP plugins_replication_replication_latency_destinationName Generated from Dropwizard metric import (metric=plugins/replication/replication_latency/destinationName, type=com.codahale.metrics.Timer)
+# TYPE plugins_replication_replication_latency_destinationName summary
+plugins_replication_replication_latency_destinationName{quantile="0.5",} 0.21199641400000002
+plugins_replication_replication_latency_destinationName{quantile="0.75",} 0.321083881
+plugins_replication_replication_latency_destinationName{quantile="0.95",} 0.321083881
+plugins_replication_replication_latency_destinationName{quantile="0.98",} 0.321083881
+plugins_replication_replication_latency_destinationName{quantile="0.99",} 0.321083881
+plugins_replication_replication_latency_destinationName{quantile="0.999",} 0.321083881
+plugins_replication_replication_latency_destinationName_count 2.0
+
+# HELP plugins_replication_latency_slower_than_60_destinationName_projectName Generated from Dropwizard metric import (metric=plugins/replication/latency_slower_than/60/destinationName/projectName, type=com.codahale.metrics.Histogram)
+# TYPE plugins_replication_latency_slower_than_60_destinationName_projectName summary
+plugins_replication_latency_slower_than_60_destinationName_projectName{quantile="0.5",} 278.0
+plugins_replication_latency_slower_than_60_destinationName_projectName{quantile="0.75",} 278.0
+plugins_replication_latency_slower_than_60_destinationName_projectName{quantile="0.95",} 278.0
+plugins_replication_latency_slower_than_60_destinationName_projectName{quantile="0.98",} 278.0
+plugins_replication_latency_slower_than_60_destinationName_projectName{quantile="0.99",} 278.0
+plugins_replication_latency_slower_than_60_destinationName_projectName{quantile="0.999",} 278.0
+plugins_replication_latency_slower_than_60_destinationName_projectName 1.0
+```
\ No newline at end of file
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 77dc1cc..3932fb3 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/AbstractConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/AbstractConfigTest.java
@@ -16,26 +16,28 @@
import static com.google.common.truth.Truth.assertThat;
import static java.nio.file.Files.createTempDirectory;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.getCurrentArguments;
-import static org.easymock.EasyMock.isA;
-import static org.easymock.EasyMock.replay;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import com.google.common.eventbus.EventBus;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.WorkQueue;
import com.google.inject.Injector;
import com.google.inject.Module;
+import com.google.inject.util.Providers;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
-import org.easymock.IAnswer;
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.junit.Before;
import org.junit.Ignore;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
@Ignore
public abstract class AbstractConfigTest {
@@ -43,6 +45,10 @@
protected final SitePaths sitePaths;
protected final Destination.Factory destinationFactoryMock;
protected final Path pluginDataPath;
+ protected ReplicationQueue replicationQueueMock;
+ protected WorkQueue workQueueMock;
+ protected EventBus eventBus = new EventBus();
+ protected FakeExecutorService executorService = new FakeExecutorService();
static class FakeDestination extends Destination {
public final DestinationConfiguration config;
@@ -53,11 +59,9 @@
}
private static Injector injectorMock() {
- Injector injector = createNiceMock(Injector.class);
- Injector childInjectorMock = createNiceMock(Injector.class);
- expect(injector.createChildInjector((Module) anyObject())).andReturn(childInjectorMock);
- replay(childInjectorMock);
- replay(injector);
+ Injector injector = mock(Injector.class);
+ Injector childInjectorMock = mock(Injector.class);
+ when(injector.createChildInjector(any(Module.class))).thenReturn(childInjectorMock);
return injector;
}
}
@@ -66,21 +70,25 @@
sitePath = createTempPath("site");
sitePaths = new SitePaths(sitePath);
pluginDataPath = createTempPath("data");
- destinationFactoryMock = createMock(Destination.Factory.class);
+ destinationFactoryMock = mock(Destination.Factory.class);
}
@Before
public void setup() {
- expect(destinationFactoryMock.create(isA(DestinationConfiguration.class)))
- .andAnswer(
- new IAnswer<Destination>() {
+ when(destinationFactoryMock.create(any(DestinationConfiguration.class)))
+ .thenAnswer(
+ new Answer<Destination>() {
@Override
- public Destination answer() throws Throwable {
- return new FakeDestination((DestinationConfiguration) getCurrentArguments()[0]);
+ public Destination answer(InvocationOnMock invocation) throws Throwable {
+ return new FakeDestination((DestinationConfiguration) invocation.getArguments()[0]);
}
- })
- .anyTimes();
- replay(destinationFactoryMock);
+ });
+
+ replicationQueueMock = mock(ReplicationQueue.class);
+ when(replicationQueueMock.isRunning()).thenReturn(Boolean.TRUE);
+
+ workQueueMock = mock(WorkQueue.class);
+ when(workQueueMock.createQueue(anyInt(), any(String.class))).thenReturn(executorService);
}
protected static Path createTempPath(String prefix) throws IOException {
@@ -113,4 +121,13 @@
assertThatIsDestination(matchingDestinations.get(0), remoteName, remoteUrls);
}
+
+ protected DestinationsCollection newDestinationsCollections(
+ ReplicationFileBasedConfig replicationFileBasedConfig) throws ConfigInvalidException {
+ return new DestinationsCollection(
+ destinationFactoryMock,
+ Providers.of(replicationQueueMock),
+ replicationFileBasedConfig,
+ eventBus);
+ }
}
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 211cafa..4b16314 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecoratorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecoratorTest.java
@@ -15,127 +15,18 @@
package com.googlesource.gerrit.plugins.replication;
import static com.google.common.truth.Truth.assertThat;
-import static org.easymock.EasyMock.anyInt;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import com.google.gerrit.server.git.WorkQueue;
import com.google.inject.util.Providers;
import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
import java.io.IOException;
-import java.util.Collection;
import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.junit.Before;
import org.junit.Test;
public class AutoReloadConfigDecoratorTest extends AbstractConfigTest {
- private AutoReloadConfigDecorator autoReloadConfig;
- private ReplicationQueue replicationQueueMock;
- private WorkQueue workQueueMock;
- private FakeExecutorService executorService = new FakeExecutorService();
-
- public class FakeExecutorService implements ScheduledExecutorService {
- public Runnable refreshCommand;
-
- @Override
- public void shutdown() {}
-
- @Override
- public List<Runnable> shutdownNow() {
- return null;
- }
-
- @Override
- public boolean isShutdown() {
- return false;
- }
-
- @Override
- public boolean isTerminated() {
- return false;
- }
-
- @Override
- public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
- return false;
- }
-
- @Override
- public <T> Future<T> submit(Callable<T> task) {
- return null;
- }
-
- @Override
- public <T> Future<T> submit(Runnable task, T result) {
- return null;
- }
-
- @Override
- public Future<?> submit(Runnable task) {
- return null;
- }
-
- @Override
- public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
- throws InterruptedException {
- return null;
- }
-
- @Override
- public <T> List<Future<T>> invokeAll(
- Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
- throws InterruptedException {
- return null;
- }
-
- @Override
- public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
- throws InterruptedException, ExecutionException {
- return null;
- }
-
- @Override
- public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
- throws InterruptedException, ExecutionException, TimeoutException {
- return null;
- }
-
- @Override
- public void execute(Runnable command) {}
-
- @Override
- public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
- return null;
- }
-
- @Override
- public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
- return null;
- }
-
- @Override
- public ScheduledFuture<?> scheduleAtFixedRate(
- Runnable command, long initialDelay, long period, TimeUnit unit) {
- refreshCommand = command;
- return null;
- }
-
- @Override
- public ScheduledFuture<?> scheduleWithFixedDelay(
- Runnable command, long initialDelay, long delay, TimeUnit unit) {
- return null;
- }
- }
+ ReplicationFileBasedConfig replicationFileBasedConfig;
public AutoReloadConfigDecoratorTest() throws IOException {
super();
@@ -146,39 +37,7 @@
public void setup() {
super.setup();
- setupMocks();
- }
-
- private void setupMocks() {
- replicationQueueMock = createNiceMock(ReplicationQueue.class);
- expect(replicationQueueMock.isRunning()).andReturn(true);
- replay(replicationQueueMock);
-
- workQueueMock = createNiceMock(WorkQueue.class);
- expect(workQueueMock.createQueue(anyInt(), anyObject(String.class))).andReturn(executorService);
- replay(workQueueMock);
- }
-
- @Test
- public void shouldLoadNotEmptyInitialReplicationConfig() throws Exception {
- FileBasedConfig replicationConfig = newReplicationConfig();
- String remoteName = "foo";
- String remoteUrl = "ssh://git@git.somewhere.com/${name}";
- replicationConfig.setString("remote", remoteName, "url", remoteUrl);
- replicationConfig.save();
-
- autoReloadConfig =
- new AutoReloadConfigDecorator(
- sitePaths,
- destinationFactoryMock,
- Providers.of(replicationQueueMock),
- pluginDataPath,
- "replication",
- workQueueMock);
-
- List<Destination> destinations = autoReloadConfig.getDestinations(FilterType.ALL);
- assertThat(destinations).hasSize(1);
- assertThatIsDestination(destinations.get(0), remoteName, remoteUrl);
+ replicationFileBasedConfig = newReplicationFileBasedConfig();
}
@Test
@@ -190,17 +49,12 @@
replicationConfig.setString("remote", remoteName1, "url", remoteUrl1);
replicationConfig.save();
- autoReloadConfig =
- new AutoReloadConfigDecorator(
- sitePaths,
- destinationFactoryMock,
- Providers.of(replicationQueueMock),
- pluginDataPath,
- "replication",
- workQueueMock);
- autoReloadConfig.startup(workQueueMock);
+ newAutoReloadConfig().start();
- List<Destination> destinations = autoReloadConfig.getDestinations(FilterType.ALL);
+ DestinationsCollection destinationsCollections =
+ newDestinationsCollections(replicationFileBasedConfig);
+ destinationsCollections.startup(workQueueMock);
+ List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(1);
assertThatIsDestination(destinations.get(0), remoteName1, remoteUrl1);
@@ -212,7 +66,7 @@
replicationConfig.save();
executorService.refreshCommand.run();
- destinations = autoReloadConfig.getDestinations(FilterType.ALL);
+ destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(2);
assertThatContainsDestination(destinations, remoteName1, remoteUrl1);
assertThatContainsDestination(destinations, remoteName2, remoteUrl2);
@@ -227,17 +81,10 @@
replicationConfig.setString("remote", remoteName1, "url", remoteUrl1);
replicationConfig.save();
- autoReloadConfig =
- new AutoReloadConfigDecorator(
- sitePaths,
- destinationFactoryMock,
- Providers.of(replicationQueueMock),
- pluginDataPath,
- "replication",
- workQueueMock);
- autoReloadConfig.startup(workQueueMock);
-
- List<Destination> destinations = autoReloadConfig.getDestinations(FilterType.ALL);
+ DestinationsCollection destinationsCollections =
+ newDestinationsCollections(replicationFileBasedConfig);
+ destinationsCollections.startup(workQueueMock);
+ List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(1);
assertThatIsDestination(destinations.get(0), remoteName1, remoteUrl1);
@@ -247,6 +94,22 @@
replicationConfig.save();
executorService.refreshCommand.run();
- assertThat(autoReloadConfig.getDestinations(FilterType.ALL)).isEqualTo(destinations);
+ assertThat(destinationsCollections.getAll(FilterType.ALL)).isEqualTo(destinations);
+ }
+
+ private AutoReloadConfigDecorator newAutoReloadConfig() {
+ AutoReloadRunnable autoReloadRunnable =
+ new AutoReloadRunnable(
+ replicationFileBasedConfig,
+ sitePaths,
+ pluginDataPath,
+ eventBus,
+ Providers.of(replicationQueueMock));
+ return new AutoReloadConfigDecorator(
+ "replication", workQueueMock, replicationFileBasedConfig, autoReloadRunnable, eventBus);
+ }
+
+ private ReplicationFileBasedConfig newReplicationFileBasedConfig() {
+ return new ReplicationFileBasedConfig(sitePaths, pluginDataPath);
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/FakeExecutorService.java b/src/test/java/com/googlesource/gerrit/plugins/replication/FakeExecutorService.java
new file mode 100644
index 0000000..2f7059e
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/FakeExecutorService.java
@@ -0,0 +1,118 @@
+// Copyright (C) 2019 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.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class FakeExecutorService implements ScheduledExecutorService {
+ public Runnable refreshCommand = () -> {};
+
+ @Override
+ public void shutdown() {}
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ return false;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return null;
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return null;
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return null;
+ }
+
+ @Override
+ public void execute(Runnable command) {}
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(
+ Runnable command, long initialDelay, long period, TimeUnit unit) {
+ refreshCommand = command;
+ return null;
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(
+ Runnable command, long initialDelay, long delay, TimeUnit unit) {
+ return null;
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java
index 0f6d629..2ee9a39 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java
@@ -14,11 +14,10 @@
package com.googlesource.gerrit.plugins.replication;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import com.google.gerrit.server.events.EventDispatcher;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -36,14 +35,12 @@
@Before
public void setUp() throws Exception {
- dispatcherMock = createMock(EventDispatcher.class);
- replay(dispatcherMock);
+ dispatcherMock = mock(EventDispatcher.class);
gitUpdateProcessing = new GitUpdateProcessing(dispatcherMock);
}
@Test
public void headRefReplicated() throws URISyntaxException, PermissionBackendException {
- reset(dispatcherMock);
RefReplicatedEvent expectedEvent =
new RefReplicatedEvent(
"someProject",
@@ -51,9 +48,6 @@
"someHost",
RefPushResult.SUCCEEDED,
RemoteRefUpdate.Status.OK);
- dispatcherMock.postEvent(RefReplicatedEventEquals.eqEvent(expectedEvent));
- expectLastCall().once();
- replay(dispatcherMock);
gitUpdateProcessing.onRefReplicatedToOneNode(
"someProject",
@@ -61,12 +55,11 @@
new URIish("git://someHost/someProject.git"),
RefPushResult.SUCCEEDED,
RemoteRefUpdate.Status.OK);
- verify(dispatcherMock);
+ verify(dispatcherMock, times(1)).postEvent(eq(expectedEvent));
}
@Test
public void changeRefReplicated() throws URISyntaxException, PermissionBackendException {
- reset(dispatcherMock);
RefReplicatedEvent expectedEvent =
new RefReplicatedEvent(
"someProject",
@@ -74,9 +67,6 @@
"someHost",
RefPushResult.FAILED,
RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD);
- dispatcherMock.postEvent(RefReplicatedEventEquals.eqEvent(expectedEvent));
- expectLastCall().once();
- replay(dispatcherMock);
gitUpdateProcessing.onRefReplicatedToOneNode(
"someProject",
@@ -84,19 +74,15 @@
new URIish("git://someHost/someProject.git"),
RefPushResult.FAILED,
RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD);
- verify(dispatcherMock);
+ verify(dispatcherMock, times(1)).postEvent(eq(expectedEvent));
}
@Test
public void onAllNodesReplicated() throws PermissionBackendException {
- reset(dispatcherMock);
RefReplicationDoneEvent expectedDoneEvent =
new RefReplicationDoneEvent("someProject", "refs/heads/master", 5);
- dispatcherMock.postEvent(RefReplicationDoneEventEquals.eqEvent(expectedDoneEvent));
- expectLastCall().once();
- replay(dispatcherMock);
gitUpdateProcessing.onRefReplicatedToAllNodes("someProject", "refs/heads/master", 5);
- verify(dispatcherMock);
+ verify(dispatcherMock, times(1)).postEvent(eq(expectedDoneEvent));
}
}
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 c010ddb..ff902cf 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/PushOneTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/PushOneTest.java
@@ -14,14 +14,13 @@
package com.googlesource.gerrit.plugins.replication;
-import static com.googlesource.gerrit.plugins.replication.RemoteRefUpdateCollectionMatcher.eqRemoteRef;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.getCurrentArguments;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.registration.DynamicItem;
@@ -45,8 +44,6 @@
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import junit.framework.AssertionFailedError;
-import org.easymock.IAnswer;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Config;
@@ -68,6 +65,8 @@
import org.eclipse.jgit.util.FS;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
public class PushOneTest {
private static final int TEST_PUSH_TIMEOUT_SECS = 10;
@@ -86,7 +85,7 @@
private IdGenerator idGeneratorMock;
private ReplicationStateListeners replicationStateListenersMock;
private ReplicationMetrics replicationMetricsMock;
- private Timer1.Context timerContextMock;
+ private Timer1.Context<String> timerContextMock;
private ProjectCache projectCacheMock;
private TransportFactory transportFactoryMock;
private Transport transportMock;
@@ -108,7 +107,7 @@
@Before
public void setup() throws Exception {
- projectNameKey = new Project.NameKey("fooProject");
+ projectNameKey = Project.nameKey("fooProject");
urish = new URIish("http://foo.com/fooProject.git");
newLocalRef =
@@ -126,6 +125,7 @@
setupMocks();
}
+ @SuppressWarnings("unchecked")
private void setupMocks() throws Exception {
FileBasedConfig config = new FileBasedConfig(new Config(), new File("/foo"), FS.DETECTED);
config.setString("remote", "Replication", "push", "foo");
@@ -134,8 +134,8 @@
setupRepositoryMock(config);
setupGitRepoManagerMock();
- projectStateMock = createNiceMock(ProjectState.class);
- forProjectMock = createNiceMock(ForProject.class);
+ projectStateMock = mock(ProjectState.class);
+ forProjectMock = mock(ForProject.class);
setupWithUserMock();
setupPermissionBackedMock();
@@ -144,46 +144,22 @@
setupRefSpecMock();
setupRemoteConfigMock();
- credentialsFactory = createNiceMock(CredentialsFactory.class);
+ credentialsFactory = mock(CredentialsFactory.class);
setupFetchConnectionMock();
setupPushConnectionMock();
setupRequestScopeMock();
- idGeneratorMock = createNiceMock(IdGenerator.class);
- replicationStateListenersMock = createNiceMock(ReplicationStateListeners.class);
+ idGeneratorMock = mock(IdGenerator.class);
+ replicationStateListenersMock = mock(ReplicationStateListeners.class);
- timerContextMock = createNiceMock(Timer1.Context.class);
+ timerContextMock = mock(Timer1.Context.class);
setupReplicationMetricsMock();
setupTransportMock();
setupProjectCacheMock();
- replicationConfigMock = createNiceMock(ReplicationConfig.class);
-
- replay(
- gitRepositoryManagerMock,
- refUpdateMock,
- repositoryMock,
- permissionBackendMock,
- destinationMock,
- remoteConfigMock,
- credentialsFactory,
- threadRequestScoperMock,
- idGeneratorMock,
- replicationStateListenersMock,
- replicationMetricsMock,
- projectCacheMock,
- timerContextMock,
- transportFactoryMock,
- projectStateMock,
- withUserMock,
- forProjectMock,
- fetchConnection,
- pushConnection,
- refSpecMock,
- refDatabaseMock,
- replicationConfigMock);
+ replicationConfigMock = mock(ReplicationConfig.class);
}
@Test
@@ -212,10 +188,7 @@
PushResult pushResult = new PushResult();
- expect(transportMock.push(anyObject(), eqRemoteRef(expectedUpdates)))
- .andReturn(pushResult)
- .once();
- replay(transportMock);
+ when(transportMock.push(any(), eq(expectedUpdates))).thenReturn(pushResult);
PushOne pushOne = createPushOne(replicationPushFilter);
@@ -223,8 +196,6 @@
pushOne.run();
isCallFinished.await(TEST_PUSH_TIMEOUT_SECS, TimeUnit.SECONDS);
-
- verify(transportMock);
}
@Test
@@ -241,12 +212,6 @@
}
});
- // easymock way to check if method was never called
- expect(transportMock.push(anyObject(), anyObject()))
- .andThrow(new AssertionFailedError())
- .anyTimes();
- replay(transportMock);
-
PushOne pushOne = createPushOne(replicationPushFilter);
pushOne.addRef(PushOne.ALL_REFS);
@@ -254,7 +219,7 @@
isCallFinished.await(10, TimeUnit.SECONDS);
- verify(transportMock);
+ verify(transportMock, never()).push(any(), any());
}
private PushOne createPushOne(DynamicItem<ReplicationPushFilter> replicationPushFilter) {
@@ -281,31 +246,31 @@
}
private void setupProjectCacheMock() throws IOException {
- projectCacheMock = createNiceMock(ProjectCache.class);
- expect(projectCacheMock.checkedGet(projectNameKey)).andReturn(projectStateMock);
+ projectCacheMock = mock(ProjectCache.class);
+ when(projectCacheMock.checkedGet(projectNameKey)).thenReturn(projectStateMock);
}
private void setupTransportMock() throws NotSupportedException, TransportException {
- transportMock = createNiceMock(Transport.class);
- expect(transportMock.openFetch()).andReturn(fetchConnection);
- transportFactoryMock = createNiceMock(TransportFactory.class);
- expect(transportFactoryMock.open(repositoryMock, urish)).andReturn(transportMock).anyTimes();
+ transportMock = mock(Transport.class);
+ when(transportMock.openFetch()).thenReturn(fetchConnection);
+ transportFactoryMock = mock(TransportFactory.class);
+ when(transportFactoryMock.open(repositoryMock, urish)).thenReturn(transportMock);
}
private void setupReplicationMetricsMock() {
- replicationMetricsMock = createNiceMock(ReplicationMetrics.class);
- expect(replicationMetricsMock.start(anyObject())).andReturn(timerContextMock);
+ replicationMetricsMock = mock(ReplicationMetrics.class);
+ when(replicationMetricsMock.start(any())).thenReturn(timerContextMock);
}
private void setupRequestScopeMock() {
- threadRequestScoperMock = createNiceMock(PerThreadRequestScope.Scoper.class);
- expect(threadRequestScoperMock.scope(anyObject()))
- .andAnswer(
- new IAnswer<Callable<Object>>() {
+ threadRequestScoperMock = mock(PerThreadRequestScope.Scoper.class);
+ when(threadRequestScoperMock.scope(any()))
+ .thenAnswer(
+ new Answer<Callable<Object>>() {
@SuppressWarnings("unchecked")
@Override
- public Callable<Object> answer() throws Throwable {
- Callable<Object> originalCall = (Callable<Object>) getCurrentArguments()[0];
+ public Callable<Object> answer(InvocationOnMock invocation) throws Throwable {
+ Callable<Object> originalCall = (Callable<Object>) invocation.getArguments()[0];
return new Callable<Object>() {
@Override
@@ -316,66 +281,64 @@
}
};
}
- })
- .anyTimes();
+ });
}
private void setupPushConnectionMock() {
- pushConnection = createNiceMock(PushConnection.class);
- expect(pushConnection.getRefsMap()).andReturn(remoteRefs);
+ pushConnection = mock(PushConnection.class);
+ when(pushConnection.getRefsMap()).thenReturn(remoteRefs);
}
private void setupFetchConnectionMock() {
- fetchConnection = createNiceMock(FetchConnection.class);
- expect(fetchConnection.getRefsMap()).andReturn(remoteRefs);
+ fetchConnection = mock(FetchConnection.class);
+ when(fetchConnection.getRefsMap()).thenReturn(remoteRefs);
}
private void setupRemoteConfigMock() {
- remoteConfigMock = createNiceMock(RemoteConfig.class);
- expect(remoteConfigMock.getPushRefSpecs()).andReturn(ImmutableList.of(refSpecMock));
+ remoteConfigMock = mock(RemoteConfig.class);
+ when(remoteConfigMock.getPushRefSpecs()).thenReturn(ImmutableList.of(refSpecMock));
}
private void setupRefSpecMock() {
- refSpecMock = createNiceMock(RefSpec.class);
- expect(refSpecMock.matchSource(anyObject(String.class))).andReturn(true);
- expect(refSpecMock.expandFromSource(anyObject(String.class))).andReturn(refSpecMock);
- expect(refSpecMock.getDestination()).andReturn("fooProject").anyTimes();
- expect(refSpecMock.isForceUpdate()).andReturn(false).anyTimes();
+ refSpecMock = mock(RefSpec.class);
+ when(refSpecMock.matchSource(any(String.class))).thenReturn(true);
+ when(refSpecMock.expandFromSource(any(String.class))).thenReturn(refSpecMock);
+ when(refSpecMock.getDestination()).thenReturn("fooProject");
+ when(refSpecMock.isForceUpdate()).thenReturn(false);
}
private void setupDestinationMock() {
- destinationMock = createNiceMock(Destination.class);
- expect(destinationMock.requestRunway(anyObject())).andReturn(RunwayStatus.allowed());
+ destinationMock = mock(Destination.class);
+ when(destinationMock.requestRunway(any())).thenReturn(RunwayStatus.allowed());
}
private void setupPermissionBackedMock() {
- permissionBackendMock = createNiceMock(PermissionBackend.class);
- expect(permissionBackendMock.currentUser()).andReturn(withUserMock);
+ permissionBackendMock = mock(PermissionBackend.class);
+ when(permissionBackendMock.currentUser()).thenReturn(withUserMock);
}
private void setupWithUserMock() {
- withUserMock = createNiceMock(WithUser.class);
- expect(withUserMock.project(projectNameKey)).andReturn(forProjectMock);
+ withUserMock = mock(WithUser.class);
+ when(withUserMock.project(projectNameKey)).thenReturn(forProjectMock);
}
private void setupGitRepoManagerMock() throws IOException {
- gitRepositoryManagerMock = createNiceMock(GitRepositoryManager.class);
- expect(gitRepositoryManagerMock.openRepository(projectNameKey)).andReturn(repositoryMock);
+ gitRepositoryManagerMock = mock(GitRepositoryManager.class);
+ when(gitRepositoryManagerMock.openRepository(projectNameKey)).thenReturn(repositoryMock);
}
private void setupRepositoryMock(FileBasedConfig config) throws IOException {
- repositoryMock = createNiceMock(Repository.class);
- refDatabaseMock = createNiceMock(RefDatabase.class);
- expect(repositoryMock.getConfig()).andReturn(config).anyTimes();
- expect(repositoryMock.getRefDatabase()).andReturn(refDatabaseMock);
- expect(refDatabaseMock.getRefs()).andReturn(localRefs);
- expect(repositoryMock.updateRef("fooProject")).andReturn(refUpdateMock);
+ repositoryMock = mock(Repository.class);
+ refDatabaseMock = mock(RefDatabase.class);
+ when(repositoryMock.getConfig()).thenReturn(config);
+ when(repositoryMock.getRefDatabase()).thenReturn(refDatabaseMock);
+ when(refDatabaseMock.getRefs()).thenReturn(localRefs);
+ when(repositoryMock.updateRef("fooProject")).thenReturn(refUpdateMock);
}
private void setupRefUpdateMock() {
- refUpdateMock = createNiceMock(RefUpdate.class);
- expect(refUpdateMock.getOldObjectId())
- .andReturn(ObjectId.fromString("0000000000000000000000000000000000000001"))
- .anyTimes();
+ refUpdateMock = mock(RefUpdate.class);
+ when(refUpdateMock.getOldObjectId())
+ .thenReturn(ObjectId.fromString("0000000000000000000000000000000000000001"));
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEventEquals.java b/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEventEquals.java
deleted file mode 100644
index 983e97f..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEventEquals.java
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (C) 2013 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 org.easymock.EasyMock;
-import org.easymock.IArgumentMatcher;
-
-public class RefReplicatedEventEquals implements IArgumentMatcher {
-
- private RefReplicatedEvent expected;
-
- public RefReplicatedEventEquals(RefReplicatedEvent expected) {
- this.expected = expected;
- }
-
- public static final RefReplicatedEvent eqEvent(RefReplicatedEvent refReplicatedEvent) {
- EasyMock.reportMatcher(new RefReplicatedEventEquals(refReplicatedEvent));
- return null;
- }
-
- @Override
- public boolean matches(Object actual) {
- if (!(actual instanceof RefReplicatedEvent)) {
- return false;
- }
- RefReplicatedEvent actualRefReplicatedEvent = (RefReplicatedEvent) actual;
- if (!equals(expected.project, actualRefReplicatedEvent.project)) {
- return false;
- }
- if (!equals(expected.ref, actualRefReplicatedEvent.ref)) {
- return false;
- }
- if (!equals(expected.targetNode, actualRefReplicatedEvent.targetNode)) {
- return false;
- }
- if (!equals(expected.status, actualRefReplicatedEvent.status)) {
- return false;
- }
- if (!equals(expected.refStatus, actualRefReplicatedEvent.refStatus)) {
- return false;
- }
- return true;
- }
-
- private static boolean equals(Object object1, Object object2) {
- if (object1 == object2) {
- return true;
- }
- if (object1 != null && !object1.equals(object2)) {
- return false;
- }
- return true;
- }
-
- @Override
- public void appendTo(StringBuffer buffer) {
- buffer.append("eqEvent(");
- buffer.append(expected.getClass().getName());
- buffer.append(" with project \"");
- buffer.append(expected.project);
- buffer.append("\" and ref \"");
- buffer.append(expected.ref);
- buffer.append("\" and targetNode \"");
- buffer.append(expected.targetNode);
- buffer.append("\" and status \"");
- buffer.append(expected.status);
- buffer.append("\" and refStatus \"");
- buffer.append(expected.refStatus);
- buffer.append("\")");
- }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEventEquals.java b/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEventEquals.java
deleted file mode 100644
index d1284e1..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEventEquals.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (C) 2013 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 org.easymock.EasyMock;
-import org.easymock.IArgumentMatcher;
-
-public class RefReplicationDoneEventEquals implements IArgumentMatcher {
-
- private RefReplicationDoneEvent expected;
-
- public RefReplicationDoneEventEquals(RefReplicationDoneEvent expected) {
- this.expected = expected;
- }
-
- public static final RefReplicationDoneEvent eqEvent(RefReplicationDoneEvent refReplicatedEvent) {
- EasyMock.reportMatcher(new RefReplicationDoneEventEquals(refReplicatedEvent));
- return null;
- }
-
- @Override
- public boolean matches(Object actual) {
- if (!(actual instanceof RefReplicationDoneEvent)) {
- return false;
- }
- RefReplicationDoneEvent actualRefReplicatedDoneEvent = (RefReplicationDoneEvent) actual;
- if (!equals(expected.project, actualRefReplicatedDoneEvent.project)) {
- return false;
- }
- if (!equals(expected.ref, actualRefReplicatedDoneEvent.ref)) {
- return false;
- }
- if (expected.nodesCount != actualRefReplicatedDoneEvent.nodesCount) {
- return false;
- }
- return true;
- }
-
- private static boolean equals(Object object1, Object object2) {
- if (object1 == object2) {
- return true;
- }
- if (object1 != null && !object1.equals(object2)) {
- return false;
- }
- return true;
- }
-
- @Override
- public void appendTo(StringBuffer buffer) {
- buffer.append("eqEvent(");
- buffer.append(expected.getClass().getName());
- buffer.append(" with project \"");
- buffer.append(expected.project);
- buffer.append("\" and ref \"");
- buffer.append(expected.ref);
- buffer.append("\" and nodesCount \"");
- buffer.append(expected.nodesCount);
- buffer.append("\")");
- }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/RemoteRefUpdateCollectionMatcher.java b/src/test/java/com/googlesource/gerrit/plugins/replication/RemoteRefUpdateCollectionMatcher.java
deleted file mode 100644
index 111a792..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/RemoteRefUpdateCollectionMatcher.java
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (C) 2019 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.util.Collection;
-import java.util.Objects;
-import org.easymock.EasyMock;
-import org.easymock.IArgumentMatcher;
-import org.eclipse.jgit.transport.RemoteRefUpdate;
-
-public class RemoteRefUpdateCollectionMatcher implements IArgumentMatcher {
- Collection<RemoteRefUpdate> expectedRemoteRefs;
-
- public static Collection<RemoteRefUpdate> eqRemoteRef(
- Collection<RemoteRefUpdate> expectedRemoteRefs) {
- EasyMock.reportMatcher(new RemoteRefUpdateCollectionMatcher(expectedRemoteRefs));
- return null;
- }
-
- public RemoteRefUpdateCollectionMatcher(Collection<RemoteRefUpdate> expectedRemoteRefs) {
- this.expectedRemoteRefs = expectedRemoteRefs;
- }
-
- @Override
- public boolean matches(Object argument) {
- if (!(argument instanceof Collection)) return false;
-
- @SuppressWarnings("unchecked")
- Collection<RemoteRefUpdate> refs = (Collection<RemoteRefUpdate>) argument;
-
- if (expectedRemoteRefs.size() != refs.size()) return false;
- return refs.stream()
- .allMatch(
- ref -> expectedRemoteRefs.stream().anyMatch(expectedRef -> compare(ref, expectedRef)));
- }
-
- @Override
- public void appendTo(StringBuffer buffer) {
- buffer.append("expected:" + expectedRemoteRefs.toString());
- }
-
- private boolean compare(RemoteRefUpdate ref, RemoteRefUpdate expectedRef) {
- return Objects.equals(ref.getRemoteName(), expectedRef.getRemoteName())
- && Objects.equals(ref.getStatus(), expectedRef.getStatus())
- && Objects.equals(ref.getExpectedOldObjectId(), expectedRef.getExpectedOldObjectId())
- && Objects.equals(ref.getNewObjectId(), expectedRef.getNewObjectId())
- && Objects.equals(ref.isFastForward(), expectedRef.isFastForward())
- && Objects.equals(ref.getSrcRef(), expectedRef.getSrcRef())
- && Objects.equals(ref.isForceUpdate(), expectedRef.isForceUpdate())
- && Objects.equals(ref.getMessage(), expectedRef.getMessage());
- }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfigTest.java
index 36cc209..f2b027c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfigTest.java
@@ -19,7 +19,6 @@
import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
import java.io.IOException;
import java.util.List;
-import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.junit.Test;
@@ -37,8 +36,10 @@
config.setString("remote", remoteName, "url", remoteUrl);
config.save();
- ReplicationFileBasedConfig replicationConfig = newReplicationFileBasedConfig();
- List<Destination> destinations = replicationConfig.getDestinations(FilterType.ALL);
+ DestinationsCollection destinationsCollections =
+ newDestinationsCollections(newReplicationFileBasedConfig());
+ destinationsCollections.startup(workQueueMock);
+ List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(1);
assertThatIsDestination(destinations.get(0), remoteName, remoteUrl);
@@ -55,18 +56,19 @@
config.setString("remote", remoteName2, "url", remoteUrl2);
config.save();
- ReplicationFileBasedConfig replicationConfig = newReplicationFileBasedConfig();
- List<Destination> destinations = replicationConfig.getDestinations(FilterType.ALL);
+ DestinationsCollection destinationsCollections =
+ newDestinationsCollections(newReplicationFileBasedConfig());
+ destinationsCollections.startup(workQueueMock);
+ List<Destination> destinations = destinationsCollections.getAll(FilterType.ALL);
assertThat(destinations).hasSize(2);
assertThatIsDestination(destinations.get(0), remoteName1, remoteUrl1);
assertThatIsDestination(destinations.get(1), remoteName2, remoteUrl2);
}
- private ReplicationFileBasedConfig newReplicationFileBasedConfig()
- throws ConfigInvalidException, IOException {
+ private ReplicationFileBasedConfig newReplicationFileBasedConfig() {
ReplicationFileBasedConfig replicationConfig =
- new ReplicationFileBasedConfig(sitePaths, destinationFactoryMock, pluginDataPath);
+ new ReplicationFileBasedConfig(sitePaths, pluginDataPath);
return replicationConfig;
}
}
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 a6a8ac8..eed5158 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
@@ -100,7 +100,7 @@
assertThat(listReplicationTasks("refs/meta/config")).hasSize(1);
- waitUntil(() -> projectExists(new Project.NameKey(sourceProject + "replica.git")));
+ waitUntil(() -> projectExists(Project.nameKey(sourceProject + "replica.git")));
ProjectInfo replicaProject = gApi.projects().name(sourceProject + "replica").get();
assertThat(replicaProject).isNotNull();
@@ -115,7 +115,7 @@
Result pushResult = createChange();
RevCommit sourceCommit = pushResult.getCommit();
- String sourceRef = pushResult.getPatchSet().getRefName();
+ String sourceRef = pushResult.getPatchSet().refName();
assertThat(listReplicationTasks("refs/changes/\\d*/\\d*/\\d*")).hasSize(1);
@@ -164,7 +164,7 @@
Result pushResult = createChange();
RevCommit sourceCommit = pushResult.getCommit();
- String sourceRef = pushResult.getPatchSet().getRefName();
+ String sourceRef = pushResult.getPatchSet().refName();
assertThat(listReplicationTasks("refs/changes/\\d*/\\d*/\\d*")).hasSize(2);
@@ -267,10 +267,10 @@
setReplicationDestination(remoteName, "replica", ALL_PROJECTS);
Result pushResult = createChange();
- shutdownConfig();
+ shutdownDestinations();
pushResult.getCommit();
- String sourceRef = pushResult.getPatchSet().getRefName();
+ String sourceRef = pushResult.getPatchSet().refName();
assertThrows(
InterruptedException.class,
@@ -293,10 +293,10 @@
reloadConfig();
Result pushResult = createChange();
- shutdownConfig();
+ shutdownDestinations();
RevCommit sourceCommit = pushResult.getCommit();
- String sourceRef = pushResult.getPatchSet().getRefName();
+ String sourceRef = pushResult.getPatchSet().refName();
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, sourceRef) != null);
@@ -318,7 +318,7 @@
replicationQueueStart();
RevCommit sourceCommit = pushResult.getCommit();
- String sourceRef = pushResult.getPatchSet().getRefName();
+ String sourceRef = pushResult.getPatchSet().refName();
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, sourceRef) != null);
@@ -357,6 +357,7 @@
config.setStringList("remote", remoteName, "url", replicaUrls);
config.setInt("remote", remoteName, "replicationDelay", TEST_REPLICATION_DELAY);
project.ifPresent(prj -> config.setString("remote", remoteName, "projects", prj));
+ config.setBoolean("gerrit", null, "autoReload", true);
config.save();
}
@@ -365,11 +366,11 @@
}
private void reloadConfig() {
- getAutoReloadConfigDecoratorInstance().forceReload();
+ getAutoReloadConfigDecoratorInstance().reload();
}
- private void shutdownConfig() {
- getAutoReloadConfigDecoratorInstance().shutdown();
+ private void shutdownDestinations() {
+ getInstance(DestinationsCollection.class).shutdown();
}
private void replicationQueueStart() {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStateTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStateTest.java
index 193af1e..e0de577 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStateTest.java
@@ -15,11 +15,9 @@
package com.googlesource.gerrit.plugins.replication;
import static com.google.common.truth.Truth.assertThat;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.resetToDefault;
-import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult;
import java.net.URISyntaxException;
@@ -35,8 +33,7 @@
@Before
public void setUp() throws Exception {
- pushResultProcessingMock = createNiceMock(PushResultProcessing.class);
- replay(pushResultProcessingMock);
+ pushResultProcessingMock = mock(PushResultProcessing.class);
replicationState = new ReplicationState(pushResultProcessingMock);
}
@@ -53,52 +50,36 @@
@Test
public void shouldFireOneReplicationEventWhenNothingToReplicate() {
- resetToDefault(pushResultProcessingMock);
-
- // expected event
- pushResultProcessingMock.onAllRefsReplicatedToAllNodes(0);
- replay(pushResultProcessingMock);
-
// actual test
replicationState.markAllPushTasksScheduled();
- verify(pushResultProcessingMock);
+
+ // expected event
+ verify(pushResultProcessingMock).onAllRefsReplicatedToAllNodes(0);
}
@Test
public void shouldFireEventsForReplicationOfOneRefToOneNode() throws URISyntaxException {
- resetToDefault(pushResultProcessingMock);
URIish uri = new URIish("git://someHost/someRepo.git");
- // expected events
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "someProject", "someRef", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToAllNodes("someProject", "someRef", 1);
- pushResultProcessingMock.onAllRefsReplicatedToAllNodes(1);
- replay(pushResultProcessingMock);
-
// actual test
replicationState.increasePushTaskCount("someProject", "someRef");
replicationState.markAllPushTasksScheduled();
replicationState.notifyRefReplicated(
"someProject", "someRef", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- verify(pushResultProcessingMock);
+
+ // expected events
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "someProject", "someRef", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock).onRefReplicatedToAllNodes("someProject", "someRef", 1);
+ verify(pushResultProcessingMock).onAllRefsReplicatedToAllNodes(1);
}
@Test
public void shouldFireEventsForReplicationOfOneRefToMultipleNodes() throws URISyntaxException {
- resetToDefault(pushResultProcessingMock);
URIish uri1 = new URIish("git://someHost1/someRepo.git");
URIish uri2 = new URIish("git://someHost2/someRepo.git");
- // expected events
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "someProject", "someRef", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "someProject", "someRef", uri2, RefPushResult.FAILED, RemoteRefUpdate.Status.NON_EXISTING);
- pushResultProcessingMock.onRefReplicatedToAllNodes("someProject", "someRef", 2);
- pushResultProcessingMock.onAllRefsReplicatedToAllNodes(2);
- replay(pushResultProcessingMock);
-
// actual test
replicationState.increasePushTaskCount("someProject", "someRef");
replicationState.increasePushTaskCount("someProject", "someRef");
@@ -107,33 +88,29 @@
"someProject", "someRef", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
replicationState.notifyRefReplicated(
"someProject", "someRef", uri2, RefPushResult.FAILED, RemoteRefUpdate.Status.NON_EXISTING);
- verify(pushResultProcessingMock);
+
+ // expected events
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "someProject", "someRef", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "someProject",
+ "someRef",
+ uri2,
+ RefPushResult.FAILED,
+ RemoteRefUpdate.Status.NON_EXISTING);
+ verify(pushResultProcessingMock).onRefReplicatedToAllNodes("someProject", "someRef", 2);
+ verify(pushResultProcessingMock).onAllRefsReplicatedToAllNodes(2);
}
@Test
public void shouldFireEventsForReplicationOfMultipleRefsToMultipleNodes()
throws URISyntaxException {
- resetToDefault(pushResultProcessingMock);
URIish uri1 = new URIish("git://host1/someRepo.git");
URIish uri2 = new URIish("git://host2/someRepo.git");
URIish uri3 = new URIish("git://host3/someRepo.git");
- // expected events
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "someProject", "ref1", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "someProject", "ref1", uri2, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "someProject", "ref1", uri3, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "someProject", "ref2", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "someProject", "ref2", uri2, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToAllNodes("someProject", "ref1", 3);
- pushResultProcessingMock.onRefReplicatedToAllNodes("someProject", "ref2", 2);
- pushResultProcessingMock.onAllRefsReplicatedToAllNodes(5);
- replay(pushResultProcessingMock);
-
// actual test
replicationState.increasePushTaskCount("someProject", "ref1");
replicationState.increasePushTaskCount("someProject", "ref1");
@@ -151,24 +128,32 @@
"someProject", "ref2", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
replicationState.notifyRefReplicated(
"someProject", "ref2", uri2, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- verify(pushResultProcessingMock);
+
+ // expected events
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "someProject", "ref1", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "someProject", "ref1", uri2, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "someProject", "ref1", uri3, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "someProject", "ref2", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "someProject", "ref2", uri2, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock).onRefReplicatedToAllNodes("someProject", "ref1", 3);
+ verify(pushResultProcessingMock).onRefReplicatedToAllNodes("someProject", "ref2", 2);
+ verify(pushResultProcessingMock).onAllRefsReplicatedToAllNodes(5);
}
@Test
public void shouldFireEventsForReplicationSameRefDifferentProjects() throws URISyntaxException {
- resetToDefault(pushResultProcessingMock);
URIish uri = new URIish("git://host1/someRepo.git");
- // expected events
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "project1", "ref1", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "project2", "ref2", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToAllNodes("project1", "ref1", 1);
- pushResultProcessingMock.onRefReplicatedToAllNodes("project2", "ref2", 1);
- pushResultProcessingMock.onAllRefsReplicatedToAllNodes(2);
- replay(pushResultProcessingMock);
-
// actual test
replicationState.increasePushTaskCount("project1", "ref1");
replicationState.increasePushTaskCount("project2", "ref2");
@@ -177,25 +162,24 @@
"project1", "ref1", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
replicationState.notifyRefReplicated(
"project2", "ref2", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- verify(pushResultProcessingMock);
+
+ // expected events
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "project1", "ref1", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "project2", "ref2", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock).onRefReplicatedToAllNodes("project1", "ref1", 1);
+ verify(pushResultProcessingMock).onRefReplicatedToAllNodes("project2", "ref2", 1);
+ verify(pushResultProcessingMock).onAllRefsReplicatedToAllNodes(2);
}
@Test
public void shouldFireEventsWhenSomeReplicationCompleteBeforeAllTasksAreScheduled()
throws URISyntaxException {
- resetToDefault(pushResultProcessingMock);
URIish uri1 = new URIish("git://host1/someRepo.git");
- // expected events
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "someProject", "ref1", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToOneNode(
- "someProject", "ref2", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
- pushResultProcessingMock.onRefReplicatedToAllNodes("someProject", "ref1", 1);
- pushResultProcessingMock.onRefReplicatedToAllNodes("someProject", "ref2", 1);
- pushResultProcessingMock.onAllRefsReplicatedToAllNodes(2);
- replay(pushResultProcessingMock);
-
// actual test
replicationState.increasePushTaskCount("someProject", "ref1");
replicationState.increasePushTaskCount("someProject", "ref2");
@@ -204,7 +188,17 @@
replicationState.notifyRefReplicated(
"someProject", "ref2", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
replicationState.markAllPushTasksScheduled();
- verify(pushResultProcessingMock);
+
+ // expected events
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "someProject", "ref1", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock)
+ .onRefReplicatedToOneNode(
+ "someProject", "ref2", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK);
+ verify(pushResultProcessingMock).onRefReplicatedToAllNodes("someProject", "ref1", 1);
+ verify(pushResultProcessingMock).onRefReplicatedToAllNodes("someProject", "ref2", 1);
+ verify(pushResultProcessingMock).onAllRefsReplicatedToAllNodes(2);
}
@Test