diff options
41 files changed, 930 insertions, 817 deletions
@@ -5,6 +5,4 @@ /.settings/org.eclipse.m2e.core.prefs /.idea replication.iml -/.buckd -/buck-cache -/buck-out +*.iml diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 17904c0..1792fcc 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,9 +1,9 @@ #Fri Jul 16 23:39:13 PDT 2010 eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 @@ -1,40 +0,0 @@ -include_defs('//lib/maven.defs') - -gerrit_plugin( - name = 'replication', - srcs = glob(['src/main/java/**/*.java']), - resources = glob(['src/main/resources/**/*']), - manifest_entries = [ - 'Implementation-Title: Replication plugin', - 'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/replication', - 'Gerrit-PluginName: replication', - 'Gerrit-Module: com.googlesource.gerrit.plugins.replication.ReplicationModule', - 'Gerrit-SshModule: com.googlesource.gerrit.plugins.replication.SshModule' - ], - deps = [ - ':commons-io', - ], - provided_deps = [ - '//lib:gson', - '//lib/log:log4j' - ], -) - -maven_jar( - name = 'commons-io', - id = 'commons-io:commons-io:1.4', - sha1 = 'a8762d07e76cfde2395257a5da47ba7c1dbd3dce', - license = 'Apache2.0', -) - -java_test( - name = 'replication_tests', - srcs = glob(['src/test/java/**/*.java']), - labels = ['replication'], - source_under_test = [':replication__plugin'], - deps = [ - ':replication__plugin', - '//gerrit-acceptance-framework:lib', - '//gerrit-plugin-api:lib', - ], -) @@ -0,0 +1,46 @@ +load("//tools/bzl:junit.bzl", "junit_tests") +load("//tools/bzl:plugin.bzl", "gerrit_plugin") + +gerrit_plugin( + name = "replication", + srcs = glob(["src/main/java/**/*.java"]), + manifest_entries = [ + "Implementation-Title: Replication plugin", + "Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/replication", + "Gerrit-PluginName: replication", + "Gerrit-InitStep: com.googlesource.gerrit.plugins.replication.Init", + "Gerrit-Module: com.googlesource.gerrit.plugins.replication.ReplicationModule", + "Gerrit-SshModule: com.googlesource.gerrit.plugins.replication.SshModule", + ], + resources = glob(["src/main/resources/**/*"]), + deps = [ + "//lib:commons-io", + ], +) + +junit_tests( + name = "replication_tests", + srcs = glob(["src/test/java/**/*Test.java"]), + tags = ["replication"], + visibility = ["//visibility:public"], + deps = [ + ":replication__plugin", + ":replication_util", + "//gerrit-acceptance-framework:lib", + "//gerrit-plugin-api:lib", + ], +) + +java_library( + name = "replication_util", + testonly = 1, + srcs = glob( + ["src/test/java/**/*.java"], + exclude = ["src/test/java/**/*Test.java"], + ), + deps = [ + ":replication__plugin", + "//gerrit-acceptance-framework:lib", + "//gerrit-plugin-api:lib", + ], +) 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 22b29b1..55c7072 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadConfigDecorator.java @@ -14,73 +14,43 @@ package com.googlesource.gerrit.plugins.replication; import com.google.gerrit.common.FileUtil; -import com.google.gerrit.server.PluginUser; -import com.google.gerrit.server.account.GroupBackend; -import com.google.gerrit.server.account.GroupIncludeCache; import com.google.gerrit.server.config.SitePaths; -import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.WorkQueue; import com.google.inject.Inject; -import com.google.inject.Injector; import com.google.inject.Singleton; - +import java.io.IOException; +import java.util.List; import org.eclipse.jgit.errors.ConfigInvalidException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.util.List; - @Singleton public class AutoReloadConfigDecorator implements ReplicationConfig { - private static final Logger log = LoggerFactory - .getLogger(AutoReloadConfigDecorator.class); + private static final Logger log = LoggerFactory.getLogger(AutoReloadConfigDecorator.class); private ReplicationFileBasedConfig currentConfig; private long currentConfigTs; - private final Injector injector; private final SitePaths site; - private final RemoteSiteUser.Factory remoteSiteUserFactory; - private final PluginUser pluginUser; - private final GitRepositoryManager gitRepositoryManager; - private final GroupBackend groupBackend; private final WorkQueue workQueue; - private final ReplicationStateListener stateLog; - private final GroupIncludeCache groupIncludeCache; + private final DestinationFactory destinationFactory; @Inject - public AutoReloadConfigDecorator(Injector injector, - SitePaths site, - RemoteSiteUser.Factory ruf, - PluginUser pu, - GitRepositoryManager grm, - GroupBackend gb, - WorkQueue workQueue, - ReplicationStateListener stateLog, - GroupIncludeCache groupIncludeCache) + public AutoReloadConfigDecorator( + SitePaths site, WorkQueue workQueue, DestinationFactory destinationFactory) throws ConfigInvalidException, IOException { - this.injector = injector; this.site = site; - this.remoteSiteUserFactory = ruf; - this.pluginUser = pu; - this.gitRepositoryManager = grm; - this.groupBackend = gb; - this.groupIncludeCache = groupIncludeCache; + this.destinationFactory = destinationFactory; this.currentConfig = loadConfig(); this.currentConfigTs = getLastModified(currentConfig); this.workQueue = workQueue; - this.stateLog = stateLog; } private static long getLastModified(ReplicationFileBasedConfig cfg) { return FileUtil.lastModified(cfg.getCfgPath()); } - private ReplicationFileBasedConfig loadConfig() - throws ConfigInvalidException, IOException { - return new ReplicationFileBasedConfig(injector, site, remoteSiteUserFactory, - pluginUser, gitRepositoryManager, groupBackend, stateLog, - groupIncludeCache); + private ReplicationFileBasedConfig loadConfig() throws ConfigInvalidException, IOException { + return new ReplicationFileBasedConfig(site, destinationFactory); } private synchronized boolean isAutoReload() { @@ -104,16 +74,16 @@ public class AutoReloadConfigDecorator implements ReplicationConfig { this.currentConfig = newConfig; this.currentConfigTs = lastModified; - log.info("Configuration reloaded: " - + currentConfig.getDestinations(FilterType.ALL).size() + " destinations, " - + discarded + " replication events discarded"); - + log.info( + "Configuration reloaded: " + + currentConfig.getDestinations(FilterType.ALL).size() + + " destinations, " + + discarded + + " replication events discarded"); } } } catch (Exception e) { - log.error( - "Cannot reload replication configuration: keeping existing settings", - e); + log.error("Cannot reload replication configuration: keeping existing settings", e); return; } } diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java index 3a0cc3f..f8737b6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoReloadSecureCredentialsFactoryDecorator.java @@ -18,19 +18,16 @@ import static com.google.gerrit.common.FileUtil.lastModified; import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; - -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.nio.file.Files; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public class AutoReloadSecureCredentialsFactoryDecorator implements - CredentialsFactory { - private static final Logger log = LoggerFactory - .getLogger(AutoReloadSecureCredentialsFactoryDecorator.class); +public class AutoReloadSecureCredentialsFactoryDecorator implements CredentialsFactory { + private static final Logger log = + LoggerFactory.getLogger(AutoReloadSecureCredentialsFactoryDecorator.class); private final AtomicReference<SecureCredentialsFactory> secureCredentialsFactory; private volatile long secureCredentialsFactoryLoadTs; @@ -38,13 +35,12 @@ public class AutoReloadSecureCredentialsFactoryDecorator implements private ReplicationFileBasedConfig config; @Inject - public AutoReloadSecureCredentialsFactoryDecorator(SitePaths site, - ReplicationFileBasedConfig config) throws ConfigInvalidException, - IOException { + public AutoReloadSecureCredentialsFactoryDecorator( + SitePaths site, ReplicationFileBasedConfig config) + throws ConfigInvalidException, IOException { this.site = site; this.config = config; - this.secureCredentialsFactory = - new AtomicReference<>(new SecureCredentialsFactory(site)); + this.secureCredentialsFactory = new AtomicReference<>(new SecureCredentialsFactory(site)); this.secureCredentialsFactoryLoadTs = getSecureConfigLastEditTs(); } @@ -59,21 +55,23 @@ public class AutoReloadSecureCredentialsFactoryDecorator implements public SecureCredentialsProvider create(String remoteName) { try { if (needsReload()) { - secureCredentialsFactory.compareAndSet(secureCredentialsFactory.get(), - new SecureCredentialsFactory(site)); + secureCredentialsFactory.compareAndSet( + secureCredentialsFactory.get(), new SecureCredentialsFactory(site)); secureCredentialsFactoryLoadTs = getSecureConfigLastEditTs(); log.info("secure.config reloaded as it was updated on the file system"); } } catch (Exception e) { - log.error("Unexpected error while trying to reload " - + "secure.config: keeping existing credentials", e); + log.error( + "Unexpected error while trying to reload " + + "secure.config: keeping existing credentials", + e); } return secureCredentialsFactory.get().create(remoteName); } private boolean needsReload() { - return config.getConfig().getBoolean("gerrit", "autoReload", false) && - getSecureConfigLastEditTs() != secureCredentialsFactoryLoadTs; + return config.getConfig().getBoolean("gerrit", "autoReload", false) + && getSecureConfigLastEditTs() != secureCredentialsFactoryLoadTs; } } diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/CredentialsFactory.java b/src/main/java/com/googlesource/gerrit/plugins/replication/CredentialsFactory.java index e960533..10719c1 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/CredentialsFactory.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/CredentialsFactory.java @@ -16,5 +16,4 @@ package com.googlesource.gerrit.plugins.replication; public interface CredentialsFactory { SecureCredentialsProvider create(String remoteName); - } 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 ed361a9..f8e2d7b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java @@ -14,15 +14,20 @@ package com.googlesource.gerrit.plugins.replication; +import static com.googlesource.gerrit.plugins.replication.PushResultProcessing.resolveNodeName; + import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; import com.google.common.collect.Lists; +import com.google.gerrit.common.EventDispatcher; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.extensions.config.FactoryModule; +import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.reviewdb.server.ReviewDb; @@ -45,16 +50,6 @@ import com.google.inject.Provider; import com.google.inject.Provides; import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.servlet.RequestScoped; - -import org.apache.commons.io.FilenameUtils; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.transport.RefSpec; -import org.eclipse.jgit.transport.RemoteConfig; -import org.eclipse.jgit.transport.URIish; -import org.slf4j.Logger; - import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -63,6 +58,14 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import org.apache.commons.io.FilenameUtils; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; +import org.slf4j.Logger; public class Destination { private static final Logger repLog = ReplicationQueue.repLog; @@ -76,31 +79,36 @@ public class Destination { private volatile WorkQueue.Executor pool; private final PerThreadRequestScope.Scoper threadScoper; private final DestinationConfiguration config; + private final DynamicItem<EventDispatcher> eventDispatcher; protected enum RetryReason { - TRANSPORT_ERROR, COLLISION, REPOSITORY_MISSING; + TRANSPORT_ERROR, + COLLISION, + REPOSITORY_MISSING; } public static class QueueInfo { public final Map<URIish, PushOne> pending; public final Map<URIish, PushOne> inFlight; - public QueueInfo(Map<URIish, PushOne> pending, - Map<URIish, PushOne> inFlight) { + public QueueInfo(Map<URIish, PushOne> pending, Map<URIish, PushOne> inFlight) { this.pending = ImmutableMap.copyOf(pending); this.inFlight = ImmutableMap.copyOf(inFlight); } } - protected Destination(Injector injector, + protected Destination( + Injector injector, DestinationConfiguration cfg, RemoteSiteUser.Factory replicationUserFactory, PluginUser pluginUser, GitRepositoryManager gitRepositoryManager, GroupBackend groupBackend, ReplicationStateListener stateLog, - GroupIncludeCache groupIncludeCache) { + GroupIncludeCache groupIncludeCache, + DynamicItem<EventDispatcher> eventDispatcher) { config = cfg; + this.eventDispatcher = eventDispatcher; gitManager = gitRepositoryManager; this.stateLog = stateLog; @@ -113,59 +121,62 @@ public class Destination { builder.add(g.getUUID()); addRecursiveParents(g.getUUID(), builder, groupIncludeCache); } else { - repLog.warn(String.format( - "Group \"%s\" not recognized, removing from authGroup", name)); + repLog.warn(String.format("Group \"%s\" not recognized, removing from authGroup", name)); } } - remoteUser = replicationUserFactory.create( - new ListGroupMembership(builder.build())); + remoteUser = replicationUserFactory.create(new ListGroupMembership(builder.build())); } else { remoteUser = pluginUser; } - Injector child = injector.createChildInjector(new FactoryModule() { - @Override - protected void configure() { - bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST); - bind(PerThreadRequestScope.Propagator.class); - bind(PerRequestProjectControlCache.class).in(RequestScoped.class); - - bind(Destination.class).toInstance(Destination.this); - bind(RemoteConfig.class).toInstance(config.getRemoteConfig()); - install(new FactoryModuleBuilder().build(PushOne.Factory.class)); - } - - @Provides - public PerThreadRequestScope.Scoper provideScoper( - final PerThreadRequestScope.Propagator propagator, - final Provider<RequestScopedReviewDbProvider> dbProvider) { - final RequestContext requestContext = new RequestContext() { - @Override - public CurrentUser getUser() { - return remoteUser; - } - - @Override - public Provider<ReviewDb> getReviewDbProvider() { - return dbProvider.get(); - } - }; - return new PerThreadRequestScope.Scoper() { - @Override - public <T> Callable<T> scope(Callable<T> callable) { - return propagator.scope(requestContext, callable); - } - }; - } - }); + Injector child = + injector.createChildInjector( + new FactoryModule() { + @Override + protected void configure() { + bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST); + bind(PerThreadRequestScope.Propagator.class); + bind(PerRequestProjectControlCache.class).in(RequestScoped.class); + + bind(Destination.class).toInstance(Destination.this); + bind(RemoteConfig.class).toInstance(config.getRemoteConfig()); + install(new FactoryModuleBuilder().build(PushOne.Factory.class)); + } + + @Provides + public PerThreadRequestScope.Scoper provideScoper( + final PerThreadRequestScope.Propagator propagator, + final Provider<RequestScopedReviewDbProvider> dbProvider) { + final RequestContext requestContext = + new RequestContext() { + @Override + public CurrentUser getUser() { + return remoteUser; + } + + @Override + public Provider<ReviewDb> getReviewDbProvider() { + return dbProvider.get(); + } + }; + return new PerThreadRequestScope.Scoper() { + @Override + public <T> Callable<T> scope(Callable<T> callable) { + return propagator.scope(requestContext, callable); + } + }; + } + }); projectControlFactory = child.getInstance(ProjectControl.Factory.class); opFactory = child.getInstance(PushOne.Factory.class); threadScoper = child.getInstance(PerThreadRequestScope.Scoper.class); } - private void addRecursiveParents(AccountGroup.UUID g, - Builder<AccountGroup.UUID> builder, GroupIncludeCache groupIncludeCache) { + private void addRecursiveParents( + AccountGroup.UUID g, + Builder<AccountGroup.UUID> builder, + GroupIncludeCache groupIncludeCache) { for (AccountGroup.UUID p : groupIncludeCache.parentGroupsOf(g)) { if (builder.build().contains(p)) { continue; @@ -200,52 +211,55 @@ public class Destination { } private boolean shouldReplicate(ProjectControl projectControl) { - return projectControl.isReadable() && (!projectControl.isHidden() - || config.replicateHiddenProjects()); + return projectControl.isReadable() + && (!projectControl.isHidden() || config.replicateHiddenProjects()); } - private boolean shouldReplicate(final Project.NameKey project, final String ref, - ReplicationState... states) { + private boolean shouldReplicate( + final Project.NameKey project, final String ref, ReplicationState... states) { try { - return threadScoper.scope(new Callable<Boolean>() { - @Override - public Boolean call() throws NoSuchProjectException { - ProjectControl projectControl = controlFor(project); - return shouldReplicate(projectControl) - && (PushOne.ALL_REFS.equals(ref) - || projectControl.controlForRef(ref).isVisible()); - } - }).call(); + return threadScoper + .scope( + new Callable<Boolean>() { + @Override + public Boolean call() throws NoSuchProjectException { + ProjectControl projectControl = controlFor(project); + return shouldReplicate(projectControl) + && (PushOne.ALL_REFS.equals(ref) + || projectControl.controlForRef(ref).isVisible()); + } + }) + .call(); } catch (NoSuchProjectException err) { - stateLog.error(String.format("source project %s not available", project), - err, states); + stateLog.error(String.format("source project %s not available", project), err, states); } catch (Exception e) { - throw Throwables.propagate(e); + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); } return false; } - private boolean shouldReplicate(final Project.NameKey project, - ReplicationState... states) { + private boolean shouldReplicate(final Project.NameKey project, ReplicationState... states) { try { - return threadScoper.scope(new Callable<Boolean>() { - @Override - public Boolean call() throws NoSuchProjectException { - return shouldReplicate(controlFor(project)); - } - }).call(); + return threadScoper + .scope( + new Callable<Boolean>() { + @Override + public Boolean call() throws NoSuchProjectException { + return shouldReplicate(controlFor(project)); + } + }) + .call(); } catch (NoSuchProjectException err) { - stateLog.error(String.format("source project %s not available", project), - err, states); + stateLog.error(String.format("source project %s not available", project), err, states); } catch (Exception e) { - Throwables.propagateIfPossible(e); + Throwables.throwIfUnchecked(e); throw new RuntimeException(e); } return false; } - void schedule(Project.NameKey project, String ref, URIish uri, - ReplicationState state) { + void schedule(Project.NameKey project, String ref, URIish uri, ReplicationState state) { repLog.info("scheduling replication {}:{} => {}", project, ref, uri); if (!shouldReplicate(project, ref, state)) { return; @@ -266,13 +280,11 @@ public class Destination { return; } } catch (IOException err) { - stateLog.error(String.format( - "cannot check type of project %s", project), err, state); + stateLog.error(String.format("cannot check type of project %s", project), err, state); return; } } catch (IOException err) { - stateLog.error(String.format( - "source project %s not available", project), err, state); + stateLog.error(String.format("source project %s not available", project), err, state); return; } } @@ -282,14 +294,16 @@ public class Destination { PushOne e = pending.get(uri); if (e == null) { e = opFactory.create(project, uri); + addRef(e, ref); + e.addState(ref, state); pool.schedule(e, config.getDelay(), TimeUnit.SECONDS); pending.put(uri, e); + } else if (!e.getRefs().contains(ref)) { + addRef(e, ref); + e.addState(ref, state); } - e.addRef(ref); state.increasePushTaskCount(project.get(), ref); - e.addState(ref, state); - repLog.info("scheduled {}:{} => {} to run after {}s", project, ref, - e, config.getDelay()); + repLog.info("scheduled {}:{} => {} to run after {}s", project, ref, e, config.getDelay()); } } @@ -300,32 +314,29 @@ public class Destination { } } + private void addRef(PushOne e, String ref) { + e.addRef(ref); + postEvent(e, ref); + } + /** * It schedules again a PushOp instance. - * <p> - * If the reason for rescheduling is to avoid a collision - * with an in-flight push to the same URI, we don't - * mark the operation as "retrying," and we schedule - * using the replication delay, rather than the retry - * delay. Otherwise, the operation is marked as - * "retrying" and scheduled to run following the - * minutes count determined by class attribute retryDelay. - * <p> - * In case the PushOp instance to be scheduled has same - * URI than one marked as "retrying," it adds to the one - * pending the refs list of the parameter instance. - * <p> - * In case the PushOp instance to be scheduled has the - * same URI as one pending, but not marked "retrying," it - * indicates the one pending should be canceled when it - * starts executing, removes it from pending list, and - * adds its refs to the parameter instance. The parameter - * instance is scheduled for retry. - * <p> - * Notice all operations to indicate a PushOp should be - * canceled, or it is retrying, or remove/add it from/to - * pending Map should be protected by synchronizing on the - * stateLock object. + * + * <p>If the reason for rescheduling is to avoid a collision with an in-flight push to the same + * URI, we don't mark the operation as "retrying," and we schedule using the replication delay, + * rather than the retry delay. Otherwise, the operation is marked as "retrying" and scheduled to + * run following the minutes count determined by class attribute retryDelay. + * + * <p>In case the PushOp instance to be scheduled has same URI than one marked as "retrying," it + * adds to the one pending the refs list of the parameter instance. + * + * <p>In case the PushOp instance to be scheduled has the same URI as one pending, but not marked + * "retrying," it indicates the one pending should be canceled when it starts executing, removes + * it from pending list, and adds its refs to the parameter instance. The parameter instance is + * scheduled for retry. + * + * <p>Notice all operations to indicate a PushOp should be canceled, or it is retrying, or + * remove/add it from/to pending Map should be protected by synchronizing on the stateLock object. * * @param pushOp The PushOp instance to be scheduled. */ @@ -378,13 +389,19 @@ public class Destination { pending.put(uri, pushOp); switch (reason) { case COLLISION: - pool.schedule(pushOp, config.getDelay(), TimeUnit.SECONDS); + pool.schedule(pushOp, config.getRescheduleDelay(), TimeUnit.SECONDS); break; case TRANSPORT_ERROR: case REPOSITORY_MISSING: default: if (pushOp.setToRetry()) { pool.schedule(pushOp, config.getRetryDelay(), TimeUnit.MINUTES); + } else { + pushOp.canceledByReplication(); + pending.remove(uri); + stateLog.error( + "Push to " + pushOp.getURI() + " cancelled after maximum number of retries", + pushOp.getStatesAsArray()); } break; } @@ -392,8 +409,7 @@ public class Destination { } } - ProjectControl controlFor(Project.NameKey project) - throws NoSuchProjectException { + ProjectControl controlFor(Project.NameKey project) throws NoSuchProjectException { return projectControlFactory.controlFor(project); } @@ -474,8 +490,7 @@ public class Destination { } List<URIish> getURIs(Project.NameKey project, String urlMatch) { - List<URIish> r = Lists.newArrayListWithCapacity( - config.getRemoteConfig().getURIs().size()); + List<URIish> r = Lists.newArrayListWithCapacity(config.getRemoteConfig().getURIs().size()); for (URIish uri : config.getRemoteConfig().getURIs()) { if (matches(uri, urlMatch)) { String name = project.get(); @@ -490,12 +505,11 @@ public class Destination { } else if (remoteNameStyle.equals("basenameOnly")) { name = FilenameUtils.getBaseName(name); } else if (!remoteNameStyle.equals("slash")) { - repLog.debug(String.format( - "Unknown remoteNameStyle: %s, falling back to slash", - remoteNameStyle)); + repLog.debug( + String.format("Unknown remoteNameStyle: %s, falling back to slash", remoteNameStyle)); } - String replacedPath = ReplicationQueue.replaceName(uri.getPath(), name, - isSingleProjectMatch()); + String replacedPath = + ReplicationQueue.replaceName(uri.getPath(), name, isSingleProjectMatch()); if (replacedPath != null) { uri = uri.setPath(replacedPath); r.add(uri); @@ -507,8 +521,8 @@ public class Destination { static boolean needsUrlEncoding(URIish uri) { return "http".equalsIgnoreCase(uri.getScheme()) - || "https".equalsIgnoreCase(uri.getScheme()) - || "amazon-s3".equalsIgnoreCase(uri.getScheme()); + || "https".equalsIgnoreCase(uri.getScheme()) + || "amazon-s3".equalsIgnoreCase(uri.getScheme()); } static String encode(String str) { @@ -518,9 +532,7 @@ public class Destination { // path used to the repository. Space is incorrectly encoded as '+' for this // context. In the path part of a URI space should be %20, but in form data // space is '+'. Our cleanup replace fixes these two issues. - return URLEncoder.encode(str, "UTF-8") - .replaceAll("%2[fF]", "/") - .replace("+", "%20"); + return URLEncoder.encode(str, "UTF-8").replaceAll("%2[fF]", "/").replace("+", "%20"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } @@ -560,4 +572,11 @@ public class Destination { } return uri.toString().contains(urlMatch); } + + private void postEvent(PushOne pushOp, String ref) { + Project.NameKey project = pushOp.getProjectNameKey(); + String targetNode = resolveNodeName(pushOp.getURI()); + ReplicationScheduledEvent event = new ReplicationScheduledEvent(project.get(), ref, targetNode); + eventDispatcher.get().postEvent(new Branch.NameKey(project, ref), event); + } } 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 f79f616..856ffb1 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationConfiguration.java @@ -16,12 +16,15 @@ package com.googlesource.gerrit.plugins.replication; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; - import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.transport.RemoteConfig; class DestinationConfiguration { + static final int DEFAULT_REPLICATION_DELAY = 15; + static final int DEFAULT_RESCHEDULE_DELAY = 3; + private final int delay; + private final int rescheduleDelay; private final int retryDelay; private final int lockErrorMaxRetries; private final ImmutableList<String> adminUrls; @@ -40,29 +43,23 @@ class DestinationConfiguration { DestinationConfiguration(RemoteConfig remoteConfig, Config cfg) { this.remoteConfig = remoteConfig; String name = remoteConfig.getName(); - urls = ImmutableList.copyOf( - cfg.getStringList("remote", name, "url")); - delay = Math.max(0, getInt(remoteConfig, cfg, "replicationdelay", 15)); - projects = ImmutableList.copyOf( - cfg.getStringList("remote", name, "projects")); - adminUrls = ImmutableList.copyOf( - cfg.getStringList("remote", name, "adminUrl")); + urls = ImmutableList.copyOf(cfg.getStringList("remote", name, "url")); + delay = Math.max(0, getInt(remoteConfig, cfg, "replicationdelay", DEFAULT_REPLICATION_DELAY)); + rescheduleDelay = + Math.max(3, getInt(remoteConfig, cfg, "rescheduledelay", DEFAULT_RESCHEDULE_DELAY)); + projects = ImmutableList.copyOf(cfg.getStringList("remote", name, "projects")); + adminUrls = ImmutableList.copyOf(cfg.getStringList("remote", name, "adminUrl")); retryDelay = Math.max(0, getInt(remoteConfig, cfg, "replicationretry", 1)); poolThreads = Math.max(0, getInt(remoteConfig, cfg, "threads", 1)); - authGroupNames = ImmutableList.copyOf( - cfg.getStringList("remote", name, "authGroup")); + authGroupNames = ImmutableList.copyOf(cfg.getStringList("remote", name, "authGroup")); lockErrorMaxRetries = cfg.getInt("replication", "lockErrorMaxRetries", 0); - createMissingRepos = - cfg.getBoolean("remote", name, "createMissingRepositories", true); - replicatePermissions = - cfg.getBoolean("remote", name, "replicatePermissions", true); - replicateProjectDeletions = - cfg.getBoolean("remote", name, "replicateProjectDeletions", false); - replicateHiddenProjects = - cfg.getBoolean("remote", name, "replicateHiddenProjects", false); - remoteNameStyle = MoreObjects.firstNonNull( - cfg.getString("remote", name, "remoteNameStyle"), "slash"); + createMissingRepos = cfg.getBoolean("remote", name, "createMissingRepositories", true); + replicatePermissions = cfg.getBoolean("remote", name, "replicatePermissions", true); + replicateProjectDeletions = cfg.getBoolean("remote", name, "replicateProjectDeletions", false); + replicateHiddenProjects = cfg.getBoolean("remote", name, "replicateHiddenProjects", false); + remoteNameStyle = + MoreObjects.firstNonNull(cfg.getString("remote", name, "remoteNameStyle"), "slash"); maxRetries = getInt( remoteConfig, cfg, "replicationMaxRetries", cfg.getInt("replication", "maxRetries", 0)); @@ -72,6 +69,10 @@ class DestinationConfiguration { return delay; } + public int getRescheduleDelay() { + return rescheduleDelay; + } + public int getRetryDelay() { return retryDelay; } @@ -128,8 +129,7 @@ class DestinationConfiguration { return maxRetries; } - private static int getInt( - RemoteConfig rc, Config cfg, String name, int defValue) { + private static int getInt(RemoteConfig rc, Config cfg, String name, int defValue) { return cfg.getInt("remote", rc.getName(), name, defValue); } } diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationFactory.java b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationFactory.java new file mode 100644 index 0000000..df886cb --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/DestinationFactory.java @@ -0,0 +1,70 @@ +// Copyright (C) 2016 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.gerrit.common.EventDispatcher; +import com.google.gerrit.extensions.registration.DynamicItem; +import com.google.gerrit.server.PluginUser; +import com.google.gerrit.server.account.GroupBackend; +import com.google.gerrit.server.account.GroupIncludeCache; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Singleton; + +@Singleton +public class DestinationFactory { + private final Injector injector; + private final RemoteSiteUser.Factory replicationUserFactory; + private final PluginUser pluginUser; + private final GitRepositoryManager gitRepositoryManager; + private final GroupBackend groupBackend; + private final ReplicationStateListener stateLog; + private final GroupIncludeCache groupIncludeCache; + private final DynamicItem<EventDispatcher> eventDispatcher; + + @Inject + public DestinationFactory( + Injector injector, + RemoteSiteUser.Factory replicationUserFactory, + PluginUser pluginUser, + GitRepositoryManager gitRepositoryManager, + GroupBackend groupBackend, + ReplicationStateListener stateLog, + GroupIncludeCache groupIncludeCache, + DynamicItem<EventDispatcher> eventDispatcher) { + this.injector = injector; + this.replicationUserFactory = replicationUserFactory; + this.pluginUser = pluginUser; + this.gitRepositoryManager = gitRepositoryManager; + this.groupBackend = groupBackend; + this.stateLog = stateLog; + this.groupIncludeCache = groupIncludeCache; + this.eventDispatcher = eventDispatcher; + } + + Destination create(DestinationConfiguration config) { + return new Destination( + injector, + config, + replicationUserFactory, + pluginUser, + gitRepositoryManager, + groupBackend, + stateLog, + groupIncludeCache, + eventDispatcher); + } +} diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/Init.java b/src/main/java/com/googlesource/gerrit/plugins/replication/Init.java new file mode 100644 index 0000000..a9fdb4f --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Init.java @@ -0,0 +1,70 @@ +// Copyright (C) 2017 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.DestinationConfiguration.DEFAULT_REPLICATION_DELAY; +import static com.googlesource.gerrit.plugins.replication.DestinationConfiguration.DEFAULT_RESCHEDULE_DELAY; + +import com.google.common.base.Strings; +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.pgm.init.api.ConsoleUI; +import com.google.gerrit.pgm.init.api.InitStep; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; +import java.io.File; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; + +public class Init implements InitStep { + private final String pluginName; + private final SitePaths site; + private final ConsoleUI ui; + + @Inject + Init(@PluginName String pluginName, SitePaths site, ConsoleUI ui) { + this.pluginName = pluginName; + this.site = site; + this.ui = ui; + } + + @Override + public void run() throws Exception { + File configFile = site.etc_dir.resolve(pluginName + ".config").toFile(); + if (!configFile.exists()) { + return; + } + + FileBasedConfig config = new FileBasedConfig(configFile, FS.DETECTED); + config.load(); + for (String name : config.getSubsections("remote")) { + if (!Strings.isNullOrEmpty(config.getString("remote", name, "rescheduleDelay"))) { + continue; + } + + int replicationDelay = + config.getInt("remote", name, "replicationDelay", DEFAULT_REPLICATION_DELAY); + if (replicationDelay > 0) { + int delay = Math.max(replicationDelay, DEFAULT_RESCHEDULE_DELAY); + ui.message("Setting remote.%s.rescheduleDelay = %d\n", name, delay); + config.setInt("remote", name, "rescheduleDelay", delay); + } else { + ui.message( + "INFO: Assuming default (%d s) for remote.%s.rescheduleDelay\n", + DEFAULT_RESCHEDULE_DELAY, name); + } + } + config.save(); + } +} 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 247ebf7..fa17dce 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ListCommand.java @@ -23,13 +23,10 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.inject.Inject; - import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType; - -import org.kohsuke.args4j.Option; - import java.util.Collection; import java.util.List; +import org.kohsuke.args4j.Option; @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) @CommandMetaData(name = "list", description = "List remote destination information") @@ -43,8 +40,7 @@ final class ListCommand extends SshCommand { @Option(name = "--json", usage = "output in json format") private boolean json; - @Inject - private ReplicationConfig config; + @Inject private ReplicationConfig config; @Override protected void run() { @@ -56,9 +52,7 @@ final class ListCommand extends SshCommand { } private boolean matches(String name) { - return (Strings.isNullOrEmpty(remote) - || name.contains(remote) - || name.matches(remote)); + return (Strings.isNullOrEmpty(remote) || name.contains(remote) || name.matches(remote)); } private void addProperty(JsonObject obj, String key, List<String> values) { @@ -73,14 +67,11 @@ final class ListCommand extends SshCommand { private void addQueueDetails(StringBuilder out, Collection<PushOne> values) { for (PushOne p : values) { - out.append(" ") - .append(p.toString()) - .append("\n"); + out.append(" ").append(p.toString()).append("\n"); } } - private void addQueueDetails(JsonObject obj, String key, - Collection<PushOne> values) { + private void addQueueDetails(JsonObject obj, String key, Collection<PushOne> values) { if (values.size() > 0) { JsonArray list = new JsonArray(); for (PushOne p : values) { @@ -106,42 +97,28 @@ final class ListCommand extends SshCommand { stdout.print(obj.toString() + "\n"); } else { StringBuilder out = new StringBuilder(); - out.append("Remote: ") - .append(d.getRemoteConfigName()) - .append("\n"); + out.append("Remote: ").append(d.getRemoteConfigName()).append("\n"); for (String url : d.getUrls()) { - out.append("Url: ") - .append(url) - .append("\n"); + out.append("Url: ").append(url).append("\n"); } if (detail) { for (String adminUrl : d.getAdminUrls()) { - out.append("AdminUrl: ") - .append(adminUrl) - .append("\n"); + out.append("AdminUrl: ").append(adminUrl).append("\n"); } for (String authGroup : d.getAuthGroupNames()) { - out.append("AuthGroup: ") - .append(authGroup) - .append("\n"); + out.append("AuthGroup: ").append(authGroup).append("\n"); } for (String project : d.getProjects()) { - out.append("Project: ") - .append(project) - .append("\n"); + out.append("Project: ").append(project).append("\n"); } Destination.QueueInfo q = d.getQueueInfo(); - out.append("In Flight: ") - .append(q.inFlight.size()) - .append("\n"); + out.append("In Flight: ").append(q.inFlight.size()).append("\n"); addQueueDetails(out, q.inFlight.values()); - out.append("Pending: ") - .append(q.pending.size()) - .append("\n"); + out.append("Pending: ").append(q.pending.size()).append("\n"); addQueueDetails(out, q.pending.values()); } stdout.print(out.toString() + "\n"); diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java b/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java index cad7bdd..a6b38c1 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java @@ -20,9 +20,7 @@ import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.extensions.systemstatus.ServerInformation; import com.google.inject.Inject; - import com.googlesource.gerrit.plugins.replication.PushResultProcessing.GitUpdateProcessing; - import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -56,10 +54,9 @@ public class OnStartStop implements LifecycleListener { if (srvInfo.getState() == ServerInformation.State.STARTUP && config.isReplicateAllOnPluginStart()) { - ReplicationState state = new ReplicationState( - new GitUpdateProcessing(eventDispatcher.get())); - pushAllFuture.set(pushAll.create( - null, ReplicationFilter.all(), state).schedule(30, TimeUnit.SECONDS)); + ReplicationState state = new ReplicationState(new GitUpdateProcessing(eventDispatcher.get())); + pushAllFuture.set( + pushAll.create(null, ReplicationFilter.all(), state).schedule(30, TimeUnit.SECONDS)); } } diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java b/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java index 16e1678..da32ecd 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java @@ -20,7 +20,6 @@ import com.google.gerrit.server.git.WorkQueue; import com.google.gerrit.server.project.ProjectCache; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; - import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -28,9 +27,7 @@ public class PushAll implements Runnable { private final ReplicationStateListener stateLog; public interface Factory { - PushAll create(String urlMatch, - ReplicationFilter filter, - ReplicationState state); + PushAll create(String urlMatch, ReplicationFilter filter, ReplicationState state); } private final WorkQueue workQueue; @@ -41,7 +38,8 @@ public class PushAll implements Runnable { private final ReplicationState state; @Inject - protected PushAll(WorkQueue wq, + protected PushAll( + WorkQueue wq, ProjectCache projectCache, ReplicationQueue rq, ReplicationStateListener stateLog, 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 46a87a4..525c990 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java @@ -19,7 +19,7 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; import com.google.common.base.Throwables; import com.google.common.collect.LinkedListMultimap; -import com.google.common.collect.Multimap; +import com.google.common.collect.ListMultimap; import com.google.common.collect.Sets; import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; @@ -42,10 +42,19 @@ import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.SchemaFactory; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; - import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult; import com.jcraft.jsch.JSchException; - +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.RemoteRepositoryException; @@ -65,23 +74,11 @@ import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; import org.slf4j.MDC; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicBoolean; - /** * A push to remote operation started by {@link GitReferenceUpdatedListener}. - * <p> - * Instance members are protected by the lock within PushQueue. Callers must - * take that lock to ensure they are working with a current view of the object. + * + * <p>Instance members are protected by the lock within PushQueue. Callers must take that lock to + * ensure they are working with a current view of the object. */ class PushOne implements ProjectRunnable, CanceledWhileRunning { private final ReplicationStateListener stateLog; @@ -112,8 +109,7 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { private int retryCount; private final int maxRetries; private boolean canceled; - private final Multimap<String,ReplicationState> stateMap = - LinkedListMultimap.create(); + private final ListMultimap<String, ReplicationState> stateMap = LinkedListMultimap.create(); private final int maxLockRetries; private int lockRetryCount; private final int id; @@ -122,7 +118,8 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { private final AtomicBoolean canceledWhileRunning; @Inject - PushOne(GitRepositoryManager grm, + PushOne( + GitRepositoryManager grm, SchemaFactory<ReviewDb> s, Destination p, RemoteConfig c, @@ -246,7 +243,7 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { stateMap.put(ref, state); } - Multimap<String,ReplicationState> getStates() { + ListMultimap<String, ReplicationState> getStates() { return stateMap; } @@ -261,7 +258,7 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { return states.toArray(new ReplicationState[states.size()]); } - void addStates(Multimap<String,ReplicationState> states) { + void addStates(ListMultimap<String, ReplicationState> states) { stateMap.putAll(states); } @@ -271,9 +268,11 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { private void statesCleanUp() { if (!stateMap.isEmpty() && !isRetrying()) { - for (Map.Entry<String,ReplicationState> entry : stateMap.entries()) { - entry.getValue().notifyRefReplicated(projectName.get(), entry.getKey(), uri, - RefPushResult.FAILED, null); + for (Map.Entry<String, ReplicationState> entry : stateMap.entries()) { + entry + .getValue() + .notifyRefReplicated( + projectName.get(), entry.getKey(), uri, RefPushResult.FAILED, null); } } } @@ -281,15 +280,18 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { @Override public void run() { try { - threadScoper.scope(new Callable<Void>() { - @Override - public Void call() { - runPushOperation(); - return null; - } - }).call(); + threadScoper + .scope( + new Callable<Void>() { + @Override + public Void call() { + runPushOperation(); + return null; + } + }) + .call(); } catch (Exception e) { - Throwables.propagateIfPossible(e); + Throwables.throwIfUnchecked(e); throw new RuntimeException(e); } finally { statesCleanUp(); @@ -304,8 +306,8 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { MDC.put(ID_MDC_KEY, IdGenerator.format(id)); if (!pool.requestRunway(this)) { if (!canceled) { - repLog.info("Rescheduling replication to " + uri - + " to avoid collision with an in-flight push."); + repLog.info( + "Rescheduling replication to " + uri + " to avoid collision with an in-flight push."); pool.reschedule(this, Destination.RetryReason.COLLISION); } return; @@ -320,13 +322,20 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { git = gitManager.openRepository(projectName); runImpl(); long elapsed = NANOSECONDS.toMillis(context.stop()); - repLog.info("Replication to " + uri + " completed in " - + (elapsed) + "ms, " - + (delay) + "ms delay, " + retryCount + " retries"); + repLog.info( + "Replication to " + + uri + + " completed in " + + (elapsed) + + "ms, " + + (delay) + + "ms delay, " + + retryCount + + " retries"); } catch (RepositoryNotFoundException e) { - stateLog.error("Cannot replicate " + projectName - + "; Local repository error: " - + e.getMessage(), getStatesAsArray()); + stateLog.error( + "Cannot replicate " + projectName + "; Local repository error: " + e.getMessage(), + getStatesAsArray()); } catch (RemoteRepositoryException e) { // Tried to replicate to a remote via anonymous git:// but the repository @@ -336,8 +345,7 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { if (msg.contains("access denied") || msg.contains("no such repository")) { createRepository(); } else { - repLog.error("Cannot replicate " + projectName - + "; Remote repository error: " + msg); + repLog.error("Cannot replicate " + projectName + "; Remote repository error: " + msg); } } catch (NoRemoteRepositoryException e) { @@ -346,8 +354,7 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { stateLog.error("Cannot replicate to " + uri, e, getStatesAsArray()); } catch (TransportException e) { Throwable cause = e.getCause(); - if (cause instanceof JSchException - && cause.getMessage().startsWith("UnknownHostKey:")) { + if (cause instanceof JSchException && cause.getMessage().startsWith("UnknownHostKey:")) { repLog.error("Cannot replicate to " + uri + ": " + cause.getMessage()); } else if (e instanceof LockFailureException) { lockRetryCount++; @@ -363,8 +370,11 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { pool.reschedule(this, Destination.RetryReason.TRANSPORT_ERROR); } } else { - repLog.error("Giving up after " + lockRetryCount - + " of this error during replication to " + e.getMessage()); + repLog.error( + "Giving up after " + + lockRetryCount + + " of this error during replication to " + + e.getMessage()); } } else { if (canceledWhileRunning.get()) { @@ -388,8 +398,7 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { } private void logCanceledWhileRunningException(TransportException e) { - repLog.info("Cannot replicate to " + uri + "." - + " It was canceled while running", e); + repLog.info("Cannot replicate to " + uri + "." + " It was canceled while running", e); } private void createRepository() { @@ -400,13 +409,17 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { repLog.warn("Missing repository created; retry replication to " + uri); pool.reschedule(this, Destination.RetryReason.REPOSITORY_MISSING); } else { - repLog.warn("Missing repository could not be created when replicating " + uri + - ". You can only create missing repositories locally, over SSH or when " + - "using adminUrl in replication.config. See documentation for more information."); + repLog.warn( + "Missing repository could not be created when replicating " + + uri + + ". You can only create missing repositories locally, over SSH or when " + + "using adminUrl in replication.config. See documentation for more information."); } } catch (IOException ioe) { - stateLog.error("Cannot replicate to " + uri + "; failed to create missing repository", - ioe, getStatesAsArray()); + stateLog.error( + "Cannot replicate to " + uri + "; failed to create missing repository", + ioe, + getStatesAsArray()); } } else { stateLog.error("Cannot replicate to " + uri + "; repository not found", getStatesAsArray()); @@ -440,8 +453,7 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { return tn.push(NullProgressMonitor.INSTANCE, todo); } - private List<RemoteRefUpdate> generateUpdates(Transport tn) - throws IOException { + private List<RemoteRefUpdate> generateUpdates(Transport tn) throws IOException { ProjectControl pc; try { pc = pool.controlFor(projectName); @@ -466,11 +478,12 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { } try (ReviewDb db = schema.open()) { - local = new VisibleRefFilter( - tagCache, changeNotesFactory, changeCache, git, pc, db, true) - .filter(local, true); + local = + new VisibleRefFilter(tagCache, changeNotesFactory, changeCache, git, pc, db, true) + .filter(local, true); } catch (OrmException e) { - stateLog.error("Cannot read database to replicate to " + projectName, e, getStatesAsArray()); + stateLog.error( + "Cannot read database to replicate to " + projectName, e, getStatesAsArray()); return Collections.emptyList(); } } @@ -512,8 +525,7 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { return cmds; } - private List<RemoteRefUpdate> doPushDelta(Map<String, Ref> local) - throws IOException { + private List<RemoteRefUpdate> doPushDelta(Map<String, Ref> local) throws IOException { List<RemoteRefUpdate> cmds = new ArrayList<>(); boolean noPerms = !pool.isReplicatePermissions(); for (String src : delta) { @@ -538,8 +550,8 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { } private boolean canPushRef(String ref, boolean noPerms) { - return !(noPerms && RefNames.REFS_CONFIG.equals(ref)) && - !ref.startsWith(RefNames.REFS_CACHE_AUTOMERGE); + return !(noPerms && RefNames.REFS_CONFIG.equals(ref)) + && !ref.startsWith(RefNames.REFS_CACHE_AUTOMERGE); } private Map<String, Ref> listRemote(Transport tn) @@ -567,22 +579,19 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { return null; } - private void push(List<RemoteRefUpdate> cmds, RefSpec spec, Ref src) - throws IOException { + private void push(List<RemoteRefUpdate> cmds, RefSpec spec, Ref src) throws IOException { String dst = spec.getDestination(); boolean force = spec.isForceUpdate(); cmds.add(new RemoteRefUpdate(git, src, dst, force, null, null)); } - private void delete(List<RemoteRefUpdate> cmds, RefSpec spec) - throws IOException { + private void delete(List<RemoteRefUpdate> cmds, RefSpec spec) throws IOException { String dst = spec.getDestination(); boolean force = spec.isForceUpdate(); cmds.add(new RemoteRefUpdate(git, (Ref) null, dst, force, null, null)); } - private void updateStates(Collection<RemoteRefUpdate> refUpdates) - throws LockFailureException { + private void updateStates(Collection<RemoteRefUpdate> refUpdates) throws LockFailureException { Set<String> doneRefs = new HashSet<>(); boolean anyRefFailed = false; RemoteRefUpdate.Status lastRefStatusError = RemoteRefUpdate.Status.OK; @@ -607,8 +616,10 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { case REJECTED_NODELETE: case REJECTED_NONFASTFORWARD: case REJECTED_REMOTE_CHANGED: - stateLog.error(String.format("Failed replicate of %s to %s: status %s", - u.getRemoteName(), uri, u.getStatus()), logStatesArray); + stateLog.error( + String.format( + "Failed replicate of %s to %s: status %s", u.getRemoteName(), uri, u.getStatus()), + logStatesArray); pushStatus = RefPushResult.FAILED; anyRefFailed = true; lastRefStatusError = u.getStatus(); @@ -616,16 +627,22 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { case REJECTED_OTHER_REASON: if ("non-fast-forward".equals(u.getMessage())) { - stateLog.error(String.format("Failed replicate of %s to %s" - + ", remote rejected non-fast-forward push." - + " Check receive.denyNonFastForwards variable in config file" - + " of destination repository.", u.getRemoteName(), uri), logStatesArray); + stateLog.error( + String.format( + "Failed replicate of %s to %s" + + ", remote rejected non-fast-forward push." + + " Check receive.denyNonFastForwards variable in config file" + + " of destination repository.", + u.getRemoteName(), uri), + logStatesArray); } else if ("failed to lock".equals(u.getMessage())) { throw new LockFailureException(uri, u.getMessage()); } else { - stateLog.error(String.format( - "Failed replicate of %s to %s, reason: %s", - u.getRemoteName(), uri, u.getMessage()), logStatesArray); + stateLog.error( + String.format( + "Failed replicate of %s to %s, reason: %s", + u.getRemoteName(), uri, u.getMessage()), + logStatesArray); } pushStatus = RefPushResult.FAILED; anyRefFailed = true; @@ -634,20 +651,25 @@ class PushOne implements ProjectRunnable, CanceledWhileRunning { } for (ReplicationState rs : getStatesByRef(u.getSrcRef())) { - rs.notifyRefReplicated(projectName.get(), u.getSrcRef(), - uri, pushStatus, u.getStatus()); + rs.notifyRefReplicated(projectName.get(), u.getSrcRef(), uri, pushStatus, u.getStatus()); } } doneRefs.add(ALL_REFS); for (ReplicationState rs : getStatesByRef(ALL_REFS)) { - rs.notifyRefReplicated(projectName.get(), ALL_REFS, uri, anyRefFailed - ? RefPushResult.FAILED : RefPushResult.SUCCEEDED, lastRefStatusError); + rs.notifyRefReplicated( + projectName.get(), + ALL_REFS, + uri, + anyRefFailed ? RefPushResult.FAILED : RefPushResult.SUCCEEDED, + lastRefStatusError); } for (Map.Entry<String, ReplicationState> entry : stateMap.entries()) { if (!doneRefs.contains(entry.getKey())) { - entry.getValue().notifyRefReplicated(projectName.get(), entry.getKey(), - uri, RefPushResult.NOT_ATTEMPTED, null); + entry + .getValue() + .notifyRefReplicated( + projectName.get(), entry.getKey(), uri, RefPushResult.NOT_ATTEMPTED, null); } } stateMap.clear(); diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/PushResultProcessing.java b/src/main/java/com/googlesource/gerrit/plugins/replication/PushResultProcessing.java index 6717660..0c3e158 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushResultProcessing.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushResultProcessing.java @@ -17,21 +17,22 @@ package com.googlesource.gerrit.plugins.replication; import com.google.gerrit.common.EventDispatcher; import com.google.gerrit.server.events.RefEvent; import com.google.gwtorm.server.OrmException; - import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult; - +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.URIish; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.ref.WeakReference; -import java.util.concurrent.atomic.AtomicBoolean; - public abstract class PushResultProcessing { - abstract void onRefReplicatedToOneNode(String project, String ref, - URIish uri, RefPushResult status, RemoteRefUpdate.Status refStatus); + abstract void onRefReplicatedToOneNode( + String project, + String ref, + URIish uri, + RefPushResult status, + RemoteRefUpdate.Status refStatus); abstract void onRefReplicatedToAllNodes(String project, String ref, int nodesCount); @@ -55,7 +56,7 @@ public abstract class PushResultProcessing { // Default doing nothing } - private static String resolveNodeName(URIish uri) { + static String resolveNodeName(URIish uri) { StringBuilder sb = new StringBuilder(); if (uri.isRemote()) { sb.append(uri.getHost()); @@ -78,8 +79,12 @@ public abstract class PushResultProcessing { } @Override - void onRefReplicatedToOneNode(String project, String ref, URIish uri, - RefPushResult status, RemoteRefUpdate.Status refStatus) { + void onRefReplicatedToOneNode( + String project, + String ref, + URIish uri, + RefPushResult status, + RemoteRefUpdate.Status refStatus) { StringBuilder sb = new StringBuilder(); sb.append("Replicate "); sb.append(project); @@ -162,10 +167,13 @@ public abstract class PushResultProcessing { } @Override - void onRefReplicatedToOneNode(String project, String ref, URIish uri, - RefPushResult status, RemoteRefUpdate.Status refStatus) { - postEvent(new RefReplicatedEvent(project, ref, resolveNodeName(uri), - status, refStatus)); + void onRefReplicatedToOneNode( + String project, + String ref, + URIish uri, + RefPushResult status, + RemoteRefUpdate.Status refStatus) { + postEvent(new RefReplicatedEvent(project, ref, resolveNodeName(uri), status, refStatus)); } @Override @@ -174,8 +182,7 @@ public abstract class PushResultProcessing { } @Override - void onAllRefsReplicatedToAllNodes(int totalPushTasksCount) { - } + void onAllRefsReplicatedToAllNodes(int totalPushTasksCount) {} private void postEvent(RefEvent event) { try { 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 a1c5596..364f1b4 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEvent.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEvent.java @@ -16,9 +16,7 @@ package com.googlesource.gerrit.plugins.replication; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.events.RefEvent; - import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult; - import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.RemoteRefUpdate.Status; @@ -31,8 +29,12 @@ public class RefReplicatedEvent extends RefEvent { final String status; final Status refStatus; - public RefReplicatedEvent(String project, String ref, String targetNode, - RefPushResult status, RemoteRefUpdate.Status refStatus) { + public RefReplicatedEvent( + String project, + String ref, + String targetNode, + RefPushResult status, + RemoteRefUpdate.Status refStatus) { super(TYPE); this.project = project; this.ref = ref; diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteSiteUser.java b/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteSiteUser.java index 11253c6..f3dc04d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteSiteUser.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RemoteSiteUser.java @@ -28,8 +28,8 @@ public class RemoteSiteUser extends CurrentUser { private final GroupMembership effectiveGroups; @Inject - RemoteSiteUser(CapabilityControl.Factory capabilityControlFactory, - @Assisted GroupMembership authGroups) { + RemoteSiteUser( + CapabilityControl.Factory capabilityControlFactory, @Assisted GroupMembership authGroups) { super(capabilityControlFactory); effectiveGroups = authGroups; } 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 241c881..e94abbd 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationConfig.java @@ -14,7 +14,6 @@ package com.googlesource.gerrit.plugins.replication; import com.google.gerrit.server.git.WorkQueue; - import java.util.List; public interface ReplicationConfig { @@ -36,5 +35,4 @@ public interface ReplicationConfig { int shutdown(); void startup(WorkQueue workQueue); - } 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 4b976bc..82e68ed 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFileBasedConfig.java @@ -13,20 +13,22 @@ // limitations under the License. package com.googlesource.gerrit.plugins.replication; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; +import static java.util.stream.Collectors.toList; + import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import com.google.gerrit.server.PluginUser; -import com.google.gerrit.server.account.GroupBackend; -import com.google.gerrit.server.account.GroupIncludeCache; import com.google.gerrit.server.config.SitePaths; -import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.WorkQueue; import com.google.inject.Inject; -import com.google.inject.Injector; 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.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; @@ -36,13 +38,6 @@ import org.eclipse.jgit.util.FS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Set; - @Singleton public class ReplicationFileBasedConfig implements ReplicationConfig { static final Logger log = LoggerFactory.getLogger(ReplicationFileBasedConfig.class); @@ -50,35 +45,14 @@ public class ReplicationFileBasedConfig implements ReplicationConfig { private Path cfgPath; private boolean replicateAllOnPluginStart; private boolean defaultForceUpdate; - private Injector injector; - private final RemoteSiteUser.Factory replicationUserFactory; - private final PluginUser pluginUser; - private final GitRepositoryManager gitRepositoryManager; - private final GroupBackend groupBackend; private final FileBasedConfig config; - private final ReplicationStateListener stateLog; - private final GroupIncludeCache groupIncludeCache; @Inject - public ReplicationFileBasedConfig(Injector injector, - SitePaths site, - RemoteSiteUser.Factory ruf, - PluginUser pu, - GitRepositoryManager grm, - GroupBackend gb, - ReplicationStateListener stateLog, - GroupIncludeCache groupIncludeCache) + public ReplicationFileBasedConfig(SitePaths site, DestinationFactory destinationFactory) throws ConfigInvalidException, IOException { this.cfgPath = site.etc_dir.resolve("replication.config"); - this.groupIncludeCache = groupIncludeCache; - this.injector = injector; - this.replicationUserFactory = ruf; - this.pluginUser = pu; - this.gitRepositoryManager = grm; - this.groupBackend = gb; this.config = new FileBasedConfig(cfgPath.toFile(), FS.DETECTED); - this.destinations = allDestinations(); - this.stateLog = stateLog; + this.destinations = allDestinations(destinationFactory); } /* @@ -89,40 +63,23 @@ public class ReplicationFileBasedConfig implements ReplicationConfig { */ @Override public List<Destination> getDestinations(FilterType filterType) { - Predicate<Destination> filter; + Predicate<? super Destination> filter; switch (filterType) { - case PROJECT_CREATION : - filter = new Predicate<Destination>() { - - @Override - public boolean apply(Destination dest) { - if (dest == null || !dest.isCreateMissingRepos()) { - return false; - } - return true; - } - }; + case PROJECT_CREATION: + filter = dest -> dest.isCreateMissingRepos(); break; - case PROJECT_DELETION : - filter = new Predicate<Destination>() { - - @Override - public boolean apply(Destination dest) { - if (dest == null || !dest.isReplicateProjectDeletions()) { - return false; - } - return true; - } - }; + case PROJECT_DELETION: + filter = dest -> dest.isReplicateProjectDeletions(); + break; + case ALL: + default: + filter = dest -> true; break; - case ALL : - return destinations; - default : - return destinations; } - return FluentIterable.from(destinations).filter(filter).toList(); + return destinations.stream().filter(Objects::nonNull).filter(filter).collect(toList()); } - private List<Destination> allDestinations() + + private List<Destination> allDestinations(DestinationFactory destinationFactory) throws ConfigInvalidException, IOException { if (!config.getFile().exists()) { log.warn("Config file " + config.getFile() + " does not exist; not replicating"); @@ -136,18 +93,16 @@ public class ReplicationFileBasedConfig implements ReplicationConfig { try { config.load(); } catch (ConfigInvalidException e) { - throw new ConfigInvalidException(String.format( - "Config file %s is invalid: %s", config.getFile(), e.getMessage()), 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); + throw new IOException( + String.format("Cannot read %s: %s", config.getFile(), e.getMessage()), e); } - replicateAllOnPluginStart = - config.getBoolean("gerrit", "replicateOnStartup", true); + replicateAllOnPluginStart = config.getBoolean("gerrit", "replicateOnStartup", true); - defaultForceUpdate = - config.getBoolean("gerrit", "defaultForceUpdate", false); + defaultForceUpdate = config.getBoolean("gerrit", "defaultForceUpdate", false); ImmutableList.Builder<Destination> dest = ImmutableList.builder(); for (RemoteConfig c : allRemotes(config)) { @@ -163,21 +118,21 @@ public class ReplicationFileBasedConfig implements ReplicationConfig { } if (c.getPushRefSpecs().isEmpty()) { - c.addPushRefSpec(new RefSpec().setSourceDestination("refs/*", "refs/*") - .setForceUpdate(defaultForceUpdate)); + c.addPushRefSpec( + new RefSpec() + .setSourceDestination("refs/*", "refs/*") + .setForceUpdate(defaultForceUpdate)); } - Destination destination = - new Destination(injector, new DestinationConfiguration(c, - config), replicationUserFactory, pluginUser, - gitRepositoryManager, groupBackend, stateLog, groupIncludeCache); + 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())); + throw new ConfigInvalidException( + String.format( + "remote.%s.url \"%s\" lacks ${name} placeholder in %s", + c.getName(), u, config.getFile())); } } } @@ -203,16 +158,15 @@ public class ReplicationFileBasedConfig implements ReplicationConfig { return defaultForceUpdate; } - private static List<RemoteConfig> allRemotes(FileBasedConfig cfg) - throws ConfigInvalidException { + 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())); + throw new ConfigInvalidException( + String.format("remote %s has invalid URL in %s", name, cfg.getFile())); } } return result; diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFilter.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFilter.java index 6b75d3e..7b3486b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFilter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationFilter.java @@ -16,17 +16,18 @@ package com.googlesource.gerrit.plugins.replication; import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.reviewdb.client.Project.NameKey; - import java.util.Collections; import java.util.List; public class ReplicationFilter { public enum PatternType { - REGEX, WILDCARD, EXACT_MATCH; + REGEX, + WILDCARD, + EXACT_MATCH; } public static ReplicationFilter all() { - return new ReplicationFilter(Collections.<String> emptyList()); + return new ReplicationFilter(Collections.<String>emptyList()); } public static PatternType getPatternType(String pattern) { @@ -66,8 +67,7 @@ public class ReplicationFilter { match = projectName.matches(pattern); break; case WILDCARD: - match = - projectName.startsWith(pattern.substring(0, pattern.length() - 1)); + match = projectName.startsWith(pattern.substring(0, pattern.length() - 1)); break; case EXACT_MATCH: match = projectName.equals(pattern); diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationLogFile.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationLogFile.java index fcc3437..fed09f9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationLogFile.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationLogFile.java @@ -18,15 +18,16 @@ import com.google.gerrit.extensions.systemstatus.ServerInformation; import com.google.gerrit.server.util.PluginLogFile; import com.google.gerrit.server.util.SystemLog; import com.google.inject.Inject; - import org.apache.log4j.PatternLayout; public class ReplicationLogFile extends PluginLogFile { @Inject - public ReplicationLogFile(SystemLog systemLog, - ServerInformation serverInfo) { - super(systemLog, serverInfo, ReplicationQueue.REPLICATION_LOG_NAME, + public ReplicationLogFile(SystemLog systemLog, ServerInformation serverInfo) { + super( + systemLog, + serverInfo, + ReplicationQueue.REPLICATION_LOG_NAME, new PatternLayout("[%d] [%X{" + PushOne.ID_MDC_KEY + "}] %m%n")); } } 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 a7b27a0..afc7926 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java @@ -32,30 +32,34 @@ public class ReplicationMetrics { ReplicationMetrics(MetricMaker metricMaker) { Field<String> DEST_FIELD = Field.ofString("destination"); - executionTime = metricMaker.newTimer( - "replication_latency", - new Description("Time spent pushing to remote destination.") - .setCumulative() - .setUnit(Description.Units.MILLISECONDS), - DEST_FIELD); + executionTime = + metricMaker.newTimer( + "replication_latency", + new Description("Time spent pushing to remote destination.") + .setCumulative() + .setUnit(Description.Units.MILLISECONDS), + DEST_FIELD); - executionDelay = metricMaker.newHistogram( - "replication_delay", - new Description("Time spent waiting before pushing to remote destination") - .setCumulative() - .setUnit(Description.Units.MILLISECONDS), - DEST_FIELD); + executionDelay = + metricMaker.newHistogram( + "replication_delay", + new Description("Time spent waiting before pushing to remote destination") + .setCumulative() + .setUnit(Description.Units.MILLISECONDS), + DEST_FIELD); - executionRetries = metricMaker.newHistogram( - "replication_retries", - new Description("Number of retries when pushing to remote destination") - .setCumulative() - .setUnit("retries"), - DEST_FIELD); + executionRetries = + metricMaker.newHistogram( + "replication_retries", + new Description("Number of retries when pushing to remote destination") + .setCumulative() + .setUnit("retries"), + DEST_FIELD); } /** * Start the replication latency timer for a destination. + * * @param name the destination name. * @return the timer context. */ @@ -65,6 +69,7 @@ public class ReplicationMetrics { /** * Record the replication delay and retry metrics for a destination. + * * @param name the destination name. * @param delay replication delay in milliseconds. * @param retries number of retries. @@ -73,5 +78,4 @@ public class ReplicationMetrics { executionDelay.record(name, delay); executionRetries.record(name, retries); } - } 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 5a5f3b4..f30e13d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationModule.java @@ -29,34 +29,30 @@ import com.google.inject.AbstractModule; import com.google.inject.Scopes; import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.internal.UniqueAnnotations; - import org.eclipse.jgit.transport.SshSessionFactory; class ReplicationModule extends AbstractModule { @Override protected void configure() { + bind(DestinationFactory.class).in(Scopes.SINGLETON); bind(ReplicationQueue.class).in(Scopes.SINGLETON); - DynamicSet.bind(binder(), GitReferenceUpdatedListener.class) - .to(ReplicationQueue.class); - DynamicSet.bind(binder(), NewProjectCreatedListener.class) - .to(ReplicationQueue.class); - DynamicSet.bind(binder(), ProjectDeletedListener.class) - .to(ReplicationQueue.class); - DynamicSet.bind(binder(), HeadUpdatedListener.class) - .to(ReplicationQueue.class); + DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ReplicationQueue.class); + DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(ReplicationQueue.class); + DynamicSet.bind(binder(), ProjectDeletedListener.class).to(ReplicationQueue.class); + DynamicSet.bind(binder(), HeadUpdatedListener.class).to(ReplicationQueue.class); bind(OnStartStop.class).in(Scopes.SINGLETON); + bind(LifecycleListener.class).annotatedWith(UniqueAnnotations.create()).to(OnStartStop.class); bind(LifecycleListener.class) - .annotatedWith(UniqueAnnotations.create()) - .to(OnStartStop.class); - bind(LifecycleListener.class).annotatedWith(UniqueAnnotations.create()).to( - ReplicationLogFile.class); - bind(CredentialsFactory.class).to( - AutoReloadSecureCredentialsFactoryDecorator.class).in(Scopes.SINGLETON); + .annotatedWith(UniqueAnnotations.create()) + .to(ReplicationLogFile.class); + bind(CredentialsFactory.class) + .to(AutoReloadSecureCredentialsFactoryDecorator.class) + .in(Scopes.SINGLETON); bind(CapabilityDefinition.class) - .annotatedWith(Exports.named(START_REPLICATION)) - .to(StartReplicationCapability.class); + .annotatedWith(Exports.named(START_REPLICATION)) + .to(StartReplicationCapability.class); install(new FactoryModuleBuilder().build(PushAll.Factory.class)); install(new FactoryModuleBuilder().build(RemoteSiteUser.Factory.class)); @@ -66,7 +62,7 @@ class ReplicationModule extends AbstractModule { EventTypes.register(RefReplicatedEvent.TYPE, RefReplicatedEvent.class); EventTypes.register(RefReplicationDoneEvent.TYPE, RefReplicationDoneEvent.class); - bind(SshSessionFactory.class).toProvider( - ReplicationSshSessionFactoryProvider.class); + EventTypes.register(ReplicationScheduledEvent.TYPE, ReplicationScheduledEvent.class); + bind(SshSessionFactory.class).toProvider(ReplicationSshSessionFactoryProvider.class); } } diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java index ba8e9cc..9a68d32 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java @@ -26,10 +26,15 @@ 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.googlesource.gerrit.plugins.replication.PushResultProcessing.GitUpdateProcessing; import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType; - +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.lib.Constants; @@ -44,21 +49,13 @@ import org.eclipse.jgit.util.io.StreamCopyThread; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.net.URISyntaxException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - /** Manages automatic replication to remote repositories. */ -public class ReplicationQueue implements - LifecycleListener, - GitReferenceUpdatedListener, - NewProjectCreatedListener, - ProjectDeletedListener, - HeadUpdatedListener { +public class ReplicationQueue + implements LifecycleListener, + GitReferenceUpdatedListener, + NewProjectCreatedListener, + ProjectDeletedListener, + HeadUpdatedListener { static final String REPLICATION_LOG_NAME = "replication_log"; static final Logger repLog = LoggerFactory.getLogger(REPLICATION_LOG_NAME); private static final int SSH_REMOTE_TIMEOUT = 120 * 1000; @@ -84,7 +81,8 @@ public class ReplicationQueue implements private volatile boolean running; @Inject - ReplicationQueue(WorkQueue wq, + ReplicationQueue( + WorkQueue wq, ReplicationConfig rc, DynamicItem<EventDispatcher> dis, ReplicationStateListener sl, @@ -107,16 +105,14 @@ public class ReplicationQueue implements running = false; int discarded = config.shutdown(); if (discarded > 0) { - repLog.warn(String.format( - "Canceled %d replication events during shutdown", discarded)); + repLog.warn(String.format("Canceled %d replication events during shutdown", discarded)); } } - void scheduleFullSync(final Project.NameKey project, final String urlMatch, - ReplicationState state) { + void scheduleFullSync( + final Project.NameKey project, final String urlMatch, ReplicationState state) { if (!running) { - stateLog.warn("Replication plugin did not finish startup before event", - state); + stateLog.warn("Replication plugin did not finish startup before event", state); return; } @@ -131,8 +127,7 @@ public class ReplicationQueue implements @Override public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) { - ReplicationState state = - new ReplicationState(new GitUpdateProcessing(dispatcher.get())); + ReplicationState state = new ReplicationState(new GitUpdateProcessing(dispatcher.get())); if (!running) { stateLog.warn("Replication plugin did not finish startup before event", state); return; @@ -151,30 +146,28 @@ public class ReplicationQueue implements @Override public void onNewProjectCreated(NewProjectCreatedListener.Event event) { - for (URIish uri : getURIs(new Project.NameKey(event.getProjectName()), - FilterType.PROJECT_CREATION)) { + for (URIish uri : + getURIs(new Project.NameKey(event.getProjectName()), FilterType.PROJECT_CREATION)) { createProject(uri, event.getHeadName()); } } @Override public void onProjectDeleted(ProjectDeletedListener.Event event) { - for (URIish uri : getURIs(new Project.NameKey(event.getProjectName()), - FilterType.PROJECT_DELETION)) { + for (URIish uri : + getURIs(new Project.NameKey(event.getProjectName()), FilterType.PROJECT_DELETION)) { deleteProject(uri); } } @Override public void onHeadUpdated(HeadUpdatedListener.Event event) { - for (URIish uri : getURIs(new Project.NameKey(event.getProjectName()), - FilterType.ALL)) { + for (URIish uri : getURIs(new Project.NameKey(event.getProjectName()), FilterType.ALL)) { updateHead(uri, event.getNewHeadName()); } } - private Set<URIish> getURIs(Project.NameKey projectName, - FilterType filterType) { + private Set<URIish> getURIs(Project.NameKey projectName, FilterType filterType) { if (config.getDestinations(filterType).isEmpty()) { return Collections.emptySet(); } @@ -200,23 +193,19 @@ public class ReplicationQueue implements try { uri = new URIish(url); } catch (URISyntaxException e) { - repLog.warn(String.format("adminURL '%s' is invalid: %s", url, - e.getMessage())); + repLog.warn(String.format("adminURL '%s' is invalid: %s", url, e.getMessage())); continue; } - String path = replaceName(uri.getPath(), projectName.get(), - config.isSingleProjectMatch()); + String path = replaceName(uri.getPath(), projectName.get(), config.isSingleProjectMatch()); if (path == null) { - repLog.warn(String - .format("adminURL %s does not contain ${name}", uri)); + repLog.warn(String.format("adminURL %s does not contain ${name}", uri)); continue; } uri = uri.setPath(path); if (!isSSH(uri)) { - repLog.warn(String.format( - "adminURL '%s' is invalid: only SSH is supported", uri)); + repLog.warn(String.format("adminURL '%s' is invalid: only SSH is supported", uri)); continue; } @@ -249,9 +238,12 @@ public class ReplicationQueue implements createRemoteSsh(replicateURI, head); repLog.info("Created remote repository: " + replicateURI); } else { - repLog.warn(String.format("Cannot create new project on remote site %s." - + " Only local paths and SSH URLs are supported" - + " for remote repository creation", replicateURI)); + repLog.warn( + String.format( + "Cannot create new project on remote site %s." + + " Only local paths and SSH URLs are supported" + + " for remote repository creation", + replicateURI)); return false; } return true; @@ -267,16 +259,13 @@ public class ReplicationQueue implements u.link(head); } } catch (IOException e) { - repLog.error(String.format( - "Error creating local repository %s:\n", uri.getPath()), e); + repLog.error(String.format("Error creating local repository %s:\n", uri.getPath()), e); } } private void createRemoteSsh(URIish uri, String head) { String quotedPath = QuotedString.BOURNE.quote(uri.getPath()); - String cmd = "mkdir -p " + quotedPath - + " && cd " + quotedPath - + " && git init --bare"; + String cmd = "mkdir -p " + quotedPath + " && cd " + quotedPath + " && git init --bare"; if (head != null) { cmd = cmd + " && git symbolic-ref HEAD " + QuotedString.BOURNE.quote(head); } @@ -284,12 +273,14 @@ public class ReplicationQueue implements try { executeRemoteSsh(uri, cmd, errStream); } catch (IOException e) { - repLog.error(String.format( - "Error creating remote repository at %s:\n" - + " Exception: %s\n" - + " Command: %s\n" - + " Output: %s", - uri, e, cmd, errStream), e); + repLog.error( + String.format( + "Error creating remote repository at %s:\n" + + " Exception: %s\n" + + " Command: %s\n" + + " Output: %s", + uri, e, cmd, errStream), + e); } } @@ -301,9 +292,12 @@ public class ReplicationQueue implements deleteRemoteSsh(replicateURI); repLog.info("Deleted remote repository: " + replicateURI); } else { - repLog.warn(String.format("Cannot delete project on remote site %s." - + " Only local paths and SSH URLs are supported" - + " for remote repository deletion", replicateURI)); + repLog.warn( + String.format( + "Cannot delete project on remote site %s." + + " Only local paths and SSH URLs are supported" + + " for remote repository deletion", + replicateURI)); } } @@ -311,9 +305,7 @@ public class ReplicationQueue implements try { recursivelyDelete(new File(uri.getPath())); } catch (IOException e) { - repLog.error(String.format( - "Error deleting local repository %s:\n", - uri.getPath()), e); + repLog.error(String.format("Error deleting local repository %s:\n", uri.getPath()), e); } } @@ -342,12 +334,14 @@ public class ReplicationQueue implements try { executeRemoteSsh(uri, cmd, errStream); } catch (IOException e) { - repLog.error(String.format( - "Error deleting remote repository at %s:\n" - + " Exception: %s\n" - + " Command: %s\n" - + " Output: %s", - uri, e, cmd, errStream), e); + repLog.error( + String.format( + "Error deleting remote repository at %s:\n" + + " Exception: %s\n" + + " Command: %s\n" + + " Output: %s", + uri, e, cmd, errStream), + e); } } @@ -357,27 +351,31 @@ public class ReplicationQueue implements } else if (isSSH(replicateURI)) { updateHeadRemoteSsh(replicateURI, newHead); } else { - repLog.warn(String.format( - "Cannot update HEAD of project on remote site %s." - + " Only local paths and SSH URLs are supported" - + " for remote HEAD update.", replicateURI)); + repLog.warn( + String.format( + "Cannot update HEAD of project on remote site %s." + + " Only local paths and SSH URLs are supported" + + " for remote HEAD update.", + replicateURI)); } } private void updateHeadRemoteSsh(URIish uri, String newHead) { String quotedPath = QuotedString.BOURNE.quote(uri.getPath()); - String cmd = "cd " + quotedPath - + " && git symbolic-ref HEAD " + QuotedString.BOURNE.quote(newHead); + String cmd = + "cd " + quotedPath + " && git symbolic-ref HEAD " + QuotedString.BOURNE.quote(newHead); OutputStream errStream = newErrorBufferStream(); try { executeRemoteSsh(uri, cmd, errStream); } catch (IOException e) { - repLog.error(String.format( - "Error updating HEAD of remote repository at %s to %s:\n" - + " Exception: %s\n" - + " Command: %s\n" - + " Output: %s", - uri, newHead, e, cmd, errStream), e); + repLog.error( + String.format( + "Error updating HEAD of remote repository at %s to %s:\n" + + " Exception: %s\n" + + " Command: %s\n" + + " Output: %s", + uri, newHead, e, cmd, errStream), + e); } } @@ -389,20 +387,16 @@ public class ReplicationQueue implements } } catch (IOException e) { repLog.error( - String.format("Failed to update HEAD of repository %s to %s", - uri.getPath(), newHead), e); + String.format("Failed to update HEAD of repository %s to %s", uri.getPath(), newHead), e); } } - private void executeRemoteSsh(URIish uri, String cmd, - OutputStream errStream) throws IOException { + private void executeRemoteSsh(URIish uri, String cmd, OutputStream errStream) throws IOException { RemoteSession ssh = connect(uri); Process proc = ssh.exec(cmd, 0); proc.getOutputStream().close(); - StreamCopyThread out = - new StreamCopyThread(proc.getInputStream(), errStream); - StreamCopyThread err = - new StreamCopyThread(proc.getErrorStream(), errStream); + StreamCopyThread out = new StreamCopyThread(proc.getInputStream(), errStream); + StreamCopyThread err = new StreamCopyThread(proc.getErrorStream(), errStream); out.start(); err.start(); try { @@ -416,8 +410,7 @@ public class ReplicationQueue implements } private RemoteSession connect(URIish uri) throws TransportException { - return sshSessionFactoryProvider.get().getSession(uri, null, FS.DETECTED, - SSH_REMOTE_TIMEOUT); + return sshSessionFactoryProvider.get().getSession(uri, null, FS.DETECTED, SSH_REMOTE_TIMEOUT); } private static OutputStream newErrorBufferStream() { diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationScheduledEvent.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationScheduledEvent.java new file mode 100644 index 0000000..7268709 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationScheduledEvent.java @@ -0,0 +1,44 @@ +// Copyright (C) 2016 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.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.client.Project.NameKey; +import com.google.gerrit.server.events.RefEvent; + +public class ReplicationScheduledEvent extends RefEvent { + static final String TYPE = "ref-replication-scheduled"; + + final String project; + final String ref; + final String targetNode; + + public ReplicationScheduledEvent(String project, String ref, String targetNode) { + super(TYPE); + this.project = project; + this.ref = ref; + this.targetNode = targetNode; + } + + @Override + public String getRefName() { + return ref; + } + + @Override + public NameKey getProjectNameKey() { + return new Project.NameKey(project); + } +} diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationSshSessionFactoryProvider.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationSshSessionFactoryProvider.java index 42bc284..0d1aa06 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationSshSessionFactoryProvider.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationSshSessionFactoryProvider.java @@ -15,7 +15,6 @@ package com.googlesource.gerrit.plugins.replication; import com.google.inject.Provider; - import org.eclipse.jgit.transport.SshSessionFactory; class ReplicationSshSessionFactoryProvider implements Provider<SshSessionFactory> { diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationState.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationState.java index 27fe841..9a68c83 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationState.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationState.java @@ -16,13 +16,11 @@ package com.googlesource.gerrit.plugins.replication; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; - -import org.eclipse.jgit.transport.RemoteRefUpdate; -import org.eclipse.jgit.transport.URIish; - import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.transport.URIish; public class ReplicationState { private boolean allScheduled; @@ -46,6 +44,7 @@ public class ReplicationState { return replicatedNodesCount == nodesToReplicateCount; } } + private final Table<String, String, RefReplicationStatus> statusByProjectRef; private int totalPushTasksCount; private int finishedPushTasksCount; @@ -69,10 +68,13 @@ public class ReplicationState { return totalPushTasksCount != 0; } - public void notifyRefReplicated(String project, String ref, URIish uri, - RefPushResult status, RemoteRefUpdate.Status refUpdateStatus) { - pushResultProcessing.onRefReplicatedToOneNode(project, ref, uri, status, - refUpdateStatus); + public void notifyRefReplicated( + String project, + String ref, + URIish uri, + RefPushResult status, + RemoteRefUpdate.Status refUpdateStatus) { + pushResultProcessing.onRefReplicatedToOneNode(project, ref, uri, status, refUpdateStatus); RefReplicationStatus completedRefStatus = null; boolean allPushTaksCompleted = false; @@ -122,8 +124,7 @@ public class ReplicationState { } /** - * Some could be remaining if replication of a ref is completed before all - * tasks are scheduled. + * Some could be remaining if replication of a ref is completed before all tasks are scheduled. */ private void fireRemainingOnRefReplicatedToAllNodes() { for (RefReplicationStatus refStatus : statusByProjectRef.values()) { @@ -132,8 +133,8 @@ public class ReplicationState { } private void doRefPushTasksCompleted(RefReplicationStatus refStatus) { - pushResultProcessing.onRefReplicatedToAllNodes(refStatus.project, - refStatus.ref, refStatus.nodesToReplicateCount); + pushResultProcessing.onRefReplicatedToAllNodes( + refStatus.project, refStatus.ref, refStatus.nodesToReplicateCount); } private RefReplicationStatus getRefStatus(String project, String ref) { @@ -158,19 +159,13 @@ public class ReplicationState { } public enum RefPushResult { - /** - * The ref was not successfully replicated. - */ + /** The ref was not successfully replicated. */ FAILED, - /** - * The ref is not configured to be replicated. - */ + /** The ref is not configured to be replicated. */ NOT_ATTEMPTED, - /** - * The ref was successfully replicated. - */ + /** The ref was successfully replicated. */ SUCCEEDED; @Override diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateListener.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateListener.java index 4dcf5a1..8e26906 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateListener.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateListener.java @@ -14,16 +14,13 @@ package com.googlesource.gerrit.plugins.replication; -/** - * Interface for notifying replication status updates. - */ +/** Interface for notifying replication status updates. */ public interface ReplicationStateListener { /** * Notify a non-fatal replication error. * - * Replication states received a non-fatal error with an associated - * warning message. + * <p>Replication states received a non-fatal error with an associated warning message. * * @param msg message description of the error * @param states replication states impacted @@ -33,8 +30,7 @@ public interface ReplicationStateListener { /** * Notify a fatal replication error. * - * Replication states have received a fatal error and replication has - * failed. + * <p>Replication states have received a fatal error and replication has failed. * * @param msg message description of the error * @param states replication states impacted @@ -44,13 +40,11 @@ public interface ReplicationStateListener { /** * Notify a fatal replication error with the associated exception. * - * Replication states have received a fatal exception and replication has failed. + * <p>Replication states have received a fatal exception and replication has failed. * * @param msg message description of the error * @param t exception that caused the replication to fail * @param states replication states impacted */ - void error(String msg, Throwable t, - ReplicationState... states); - + void error(String msg, Throwable t, ReplicationState... states); } diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateLogger.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateLogger.java index 0a59ad3..cfa95dd 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateLogger.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStateLogger.java @@ -20,11 +20,10 @@ import com.google.inject.Singleton; /** * Wrapper around a Logger that also logs out the replication state. - * <p> - * When logging replication errors it is useful to know the current - * replication state. This utility class wraps the methods from Logger - * and logs additional information about the replication state to the - * stderr console. + * + * <p>When logging replication errors it is useful to know the current replication state. This + * utility class wraps the methods from Logger and logs additional information about the replication + * state to the stderr console. */ @Singleton class ReplicationStateLogger implements ReplicationStateListener { diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsFactory.java b/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsFactory.java index a10f62f..2b0c16b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsFactory.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsFactory.java @@ -16,37 +16,32 @@ package com.googlesource.gerrit.plugins.replication; import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; - +import java.io.IOException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; -import java.io.IOException; - /** Looks up a remote's password in secure.config. */ class SecureCredentialsFactory implements CredentialsFactory { private final Config config; @Inject - SecureCredentialsFactory(SitePaths site) - throws ConfigInvalidException, IOException { + SecureCredentialsFactory(SitePaths site) throws ConfigInvalidException, IOException { config = load(site); } - private static Config load(SitePaths site) - throws ConfigInvalidException, IOException { - FileBasedConfig cfg = - new FileBasedConfig(site.secure_config.toFile(), FS.DETECTED); + private static Config load(SitePaths site) throws ConfigInvalidException, IOException { + FileBasedConfig cfg = new FileBasedConfig(site.secure_config.toFile(), FS.DETECTED); if (cfg.getFile().exists() && cfg.getFile().length() > 0) { try { cfg.load(); } catch (ConfigInvalidException e) { - throw new ConfigInvalidException(String.format( - "Config file %s is invalid: %s", cfg.getFile(), e.getMessage()), e); + throw new ConfigInvalidException( + String.format("Config file %s is invalid: %s", cfg.getFile(), e.getMessage()), e); } catch (IOException e) { - throw new IOException(String.format( - "Cannot read %s: %s", cfg.getFile(), e.getMessage()), e); + throw new IOException( + String.format("Cannot read %s: %s", cfg.getFile(), e.getMessage()), e); } } return cfg; diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsProvider.java b/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsProvider.java index a878ed9..c4294a9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsProvider.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/SecureCredentialsProvider.java @@ -49,8 +49,7 @@ class SecureCredentialsProvider extends CredentialsProvider { } @Override - public boolean get(URIish uri, CredentialItem... items) - throws UnsupportedCredentialItem { + public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { String username = uri.getUser(); if (username == null) { username = cfgUser; diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java b/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java index be5242e..c701c21 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java @@ -18,23 +18,22 @@ import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.sshd.CommandMetaData; import com.google.gerrit.sshd.SshCommand; import com.google.inject.Inject; - import com.googlesource.gerrit.plugins.replication.PushResultProcessing.CommandProcessing; - -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; @RequiresCapability(StartReplicationCapability.START_REPLICATION) -@CommandMetaData(name = "start", description = "Start replication for specific project or all projects") +@CommandMetaData( + name = "start", + description = "Start replication for specific project or all projects" +) final class StartCommand extends SshCommand { - @Inject - private ReplicationStateLogger stateLog; + @Inject private ReplicationStateLogger stateLog; @Option(name = "--all", usage = "push all known projects") private boolean all; @@ -42,15 +41,13 @@ final class StartCommand extends SshCommand { @Option(name = "--url", metaVar = "PATTERN", usage = "pattern to match URL on") private String urlMatch; - @Option(name = "--wait", - usage = "wait for replication to finish before exiting") + @Option(name = "--wait", usage = "wait for replication to finish before exiting") private boolean wait; @Argument(index = 0, multiValued = true, metaVar = "PATTERN", usage = "project name pattern") private List<String> projectPatterns = new ArrayList<>(2); - @Inject - private PushAll.Factory pushFactory; + @Inject private PushAll.Factory pushFactory; @Override protected void run() throws Failure { @@ -76,7 +73,8 @@ final class StartCommand extends SshCommand { try { future.get(); } catch (InterruptedException e) { - stateLog.error("Thread was interrupted while waiting for PushAll operation to finish", e, state); + stateLog.error( + "Thread was interrupted while waiting for PushAll operation to finish", e, state); return; } catch (ExecutionException e) { stateLog.error("An exception was thrown in PushAll operation", e, state); diff --git a/src/main/resources/Documentation/cmd-list.md b/src/main/resources/Documentation/cmd-list.md index 3c6b78f..b6688e0 100644 --- a/src/main/resources/Documentation/cmd-list.md +++ b/src/main/resources/Documentation/cmd-list.md @@ -70,5 +70,5 @@ List destinations whose name contains mirror: SEE ALSO -------- -* [Replication Configuration](config.html) +* [Replication Configuration](config.md) * [Access Control](../../../Documentation/access-control.html) diff --git a/src/main/resources/Documentation/cmd-start.md b/src/main/resources/Documentation/cmd-start.md index 19b33ec..59c3d1d 100644 --- a/src/main/resources/Documentation/cmd-start.md +++ b/src/main/resources/Documentation/cmd-start.md @@ -134,5 +134,5 @@ Replicate projects whose path includes a folder named `vendor` to host slave1: SEE ALSO -------- -* [Replication Configuration](config.html) +* [Replication Configuration](config.md) * [Access Control](../../../Documentation/access-control.html) diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index 0577ab1..b1058b5 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md @@ -4,7 +4,7 @@ Replication Configuration Enabling Replication -------------------- -If replicating over SSH (recommended), ensure the host key of the +If replicating over SSH, ensure the host key of the remote system(s) is already in the Gerrit user's `~/.ssh/known_hosts` file. The easiest way to add the host key is to connect once by hand with the command line: @@ -39,7 +39,7 @@ Then reload the replication plugin to pick up the new configuration: ``` To manually trigger replication at runtime, see -SSH command [start](cmd-start.html). +SSH command [start](cmd-start.md). File `replication.config` ------------------------- @@ -216,6 +216,17 @@ remote.NAME.replicationDelay By default, 15 seconds. +remote.NAME.rescheduleDelay +: Delay when rescheduling a push operation due to an in-flight push + running for the same project. + + Cannot be set to a value lower than 3 seconds to avoid a tight loop + of schedule/run which could cause 1K+ retries per second. + + A configured value lower than 3 seconds will be rounded to 3 seconds. + + By default, 3 seconds. + remote.NAME.replicationRetry : Time to wait before scheduling a remote push operation previously failed due to an offline remote server. 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 4cbac3a..41829bc 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/GitUpdateProcessingTest.java @@ -28,19 +28,16 @@ import com.google.gwtorm.client.KeyUtil; import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.SchemaFactory; import com.google.gwtorm.server.StandardKeyEncoder; - import com.googlesource.gerrit.plugins.replication.PushResultProcessing.GitUpdateProcessing; import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult; - -import junit.framework.TestCase; - +import java.net.URISyntaxException; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.URIish; - -import java.net.URISyntaxException; +import org.junit.Before; +import org.junit.Test; @SuppressWarnings("unchecked") -public class GitUpdateProcessingTest extends TestCase { +public class GitUpdateProcessingTest { static { KeyUtil.setEncoderImpl(new StandardKeyEncoder()); } @@ -48,9 +45,8 @@ public class GitUpdateProcessingTest extends TestCase { private EventDispatcher dispatcherMock; private GitUpdateProcessing gitUpdateProcessing; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { dispatcherMock = createMock(EventDispatcher.class); replay(dispatcherMock); ReviewDb reviewDbMock = createNiceMock(ReviewDb.class); @@ -61,42 +57,58 @@ public class GitUpdateProcessingTest extends TestCase { gitUpdateProcessing = new GitUpdateProcessing(dispatcherMock); } - public void testHeadRefReplicated() throws URISyntaxException, OrmException { + @Test + public void headRefReplicated() throws URISyntaxException, OrmException { reset(dispatcherMock); RefReplicatedEvent expectedEvent = - new RefReplicatedEvent("someProject", "refs/heads/master", "someHost", - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + new RefReplicatedEvent( + "someProject", + "refs/heads/master", + "someHost", + RefPushResult.SUCCEEDED, + RemoteRefUpdate.Status.OK); dispatcherMock.postEvent(RefReplicatedEventEquals.eqEvent(expectedEvent)); expectLastCall().once(); replay(dispatcherMock); - gitUpdateProcessing.onRefReplicatedToOneNode("someProject", - "refs/heads/master", new URIish("git://someHost/someProject.git"), - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + gitUpdateProcessing.onRefReplicatedToOneNode( + "someProject", + "refs/heads/master", + new URIish("git://someHost/someProject.git"), + RefPushResult.SUCCEEDED, + RemoteRefUpdate.Status.OK); verify(dispatcherMock); } - public void testChangeRefReplicated() throws URISyntaxException, OrmException { + @Test + public void changeRefReplicated() throws URISyntaxException, OrmException { reset(dispatcherMock); RefReplicatedEvent expectedEvent = - new RefReplicatedEvent("someProject", "refs/changes/01/1/1", "someHost", - RefPushResult.FAILED, RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD); + new RefReplicatedEvent( + "someProject", + "refs/changes/01/1/1", + "someHost", + RefPushResult.FAILED, + RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD); dispatcherMock.postEvent(RefReplicatedEventEquals.eqEvent(expectedEvent)); expectLastCall().once(); replay(dispatcherMock); - gitUpdateProcessing.onRefReplicatedToOneNode("someProject", - "refs/changes/01/1/1", new URIish("git://someHost/someProject.git"), - RefPushResult.FAILED, RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD); + gitUpdateProcessing.onRefReplicatedToOneNode( + "someProject", + "refs/changes/01/1/1", + new URIish("git://someHost/someProject.git"), + RefPushResult.FAILED, + RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD); verify(dispatcherMock); } - public void testOnAllNodesReplicated() throws OrmException { + @Test + public void onAllNodesReplicated() throws OrmException { reset(dispatcherMock); RefReplicationDoneEvent expectedDoneEvent = new RefReplicationDoneEvent("someProject", "refs/heads/master", 5); - dispatcherMock.postEvent( - RefReplicationDoneEventEquals.eqEvent(expectedDoneEvent)); + dispatcherMock.postEvent(RefReplicationDoneEventEquals.eqEvent(expectedDoneEvent)); expectLastCall().once(); replay(dispatcherMock); diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/PushReplicationTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/PushReplicationTest.java index 71b6600..8480cbe 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/PushReplicationTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/PushReplicationTest.java @@ -18,11 +18,10 @@ import static com.google.common.truth.Truth.assertThat; import static com.googlesource.gerrit.plugins.replication.Destination.encode; import static com.googlesource.gerrit.plugins.replication.Destination.needsUrlEncoding; +import java.net.URISyntaxException; import org.eclipse.jgit.transport.URIish; import org.junit.Test; -import java.net.URISyntaxException; - public class PushReplicationTest { @Test @@ -38,7 +37,7 @@ public class PushReplicationTest { } @Test - public void testUrlEncoding() { + public void urlEncoding() { assertThat(encode("foo/bar/thing")).isEqualTo("foo/bar/thing"); assertThat(encode("-- All Projects --")).isEqualTo("--%20All%20Projects%20--"); assertThat(encode("name/with a space")).isEqualTo("name/with%20a%20space"); diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEventEquals.java b/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEventEquals.java index d614463..983e97f 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEventEquals.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicatedEventEquals.java @@ -35,7 +35,7 @@ public class RefReplicatedEventEquals implements IArgumentMatcher { if (!(actual instanceof RefReplicatedEvent)) { return false; } - RefReplicatedEvent actualRefReplicatedEvent = (RefReplicatedEvent)actual; + RefReplicatedEvent actualRefReplicatedEvent = (RefReplicatedEvent) actual; if (!equals(expected.project, actualRefReplicatedEvent.project)) { return false; } diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEventEquals.java b/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEventEquals.java index 02f96fb..d1284e1 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEventEquals.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/RefReplicationDoneEventEquals.java @@ -35,7 +35,7 @@ public class RefReplicationDoneEventEquals implements IArgumentMatcher { if (!(actual instanceof RefReplicationDoneEvent)) { return false; } - RefReplicationDoneEvent actualRefReplicatedDoneEvent = (RefReplicationDoneEvent)actual; + RefReplicationDoneEvent actualRefReplicatedDoneEvent = (RefReplicationDoneEvent) actual; if (!equals(expected.project, actualRefReplicatedDoneEvent.project)) { return false; } 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 56096c2..65e2d64 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStateTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStateTest.java @@ -22,14 +22,12 @@ import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; import com.googlesource.gerrit.plugins.replication.ReplicationState.RefPushResult; - +import java.net.URISyntaxException; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.URIish; import org.junit.Before; import org.junit.Test; -import java.net.URISyntaxException; - public class ReplicationStateTest { private ReplicationState replicationState; @@ -67,41 +65,37 @@ public class ReplicationStateTest { } @Test - public void shouldFireEventsForReplicationOfOneRefToOneNode() - throws URISyntaxException { + 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.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); + replicationState.notifyRefReplicated( + "someProject", "someRef", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); verify(pushResultProcessingMock); } @Test - public void shouldFireEventsForReplicationOfOneRefToMultipleNodes() - throws URISyntaxException { + 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.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); @@ -109,10 +103,10 @@ public class ReplicationStateTest { replicationState.increasePushTaskCount("someProject", "someRef"); replicationState.increasePushTaskCount("someProject", "someRef"); replicationState.markAllPushTasksScheduled(); - replicationState.notifyRefReplicated("someProject", "someRef", uri1, - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); - replicationState.notifyRefReplicated("someProject", "someRef", uri2, - RefPushResult.FAILED, RemoteRefUpdate.Status.NON_EXISTING); + replicationState.notifyRefReplicated( + "someProject", "someRef", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + replicationState.notifyRefReplicated( + "someProject", "someRef", uri2, RefPushResult.FAILED, RemoteRefUpdate.Status.NON_EXISTING); verify(pushResultProcessingMock); } @@ -125,20 +119,18 @@ public class ReplicationStateTest { 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.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); @@ -149,30 +141,29 @@ public class ReplicationStateTest { replicationState.increasePushTaskCount("someProject", "ref2"); replicationState.increasePushTaskCount("someProject", "ref2"); replicationState.markAllPushTasksScheduled(); - replicationState.notifyRefReplicated("someProject", "ref1", uri1, - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); - replicationState.notifyRefReplicated("someProject", "ref1", uri2, - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); - replicationState.notifyRefReplicated("someProject", "ref1", uri3, - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); - replicationState.notifyRefReplicated("someProject", "ref2", uri1, - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); - replicationState.notifyRefReplicated("someProject", "ref2", uri2, - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + replicationState.notifyRefReplicated( + "someProject", "ref1", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + replicationState.notifyRefReplicated( + "someProject", "ref1", uri2, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + replicationState.notifyRefReplicated( + "someProject", "ref1", uri3, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + replicationState.notifyRefReplicated( + "someProject", "ref2", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + replicationState.notifyRefReplicated( + "someProject", "ref2", uri2, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); verify(pushResultProcessingMock); } @Test - public void shouldFireEventsForReplicationSameRefDifferentProjects() - throws URISyntaxException { + 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.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); @@ -182,10 +173,10 @@ public class ReplicationStateTest { replicationState.increasePushTaskCount("project1", "ref1"); replicationState.increasePushTaskCount("project2", "ref2"); replicationState.markAllPushTasksScheduled(); - replicationState.notifyRefReplicated("project1", "ref1", uri, - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); - replicationState.notifyRefReplicated("project2", "ref2", uri, - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + replicationState.notifyRefReplicated( + "project1", "ref1", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + replicationState.notifyRefReplicated( + "project2", "ref2", uri, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); verify(pushResultProcessingMock); } @@ -195,25 +186,23 @@ public class ReplicationStateTest { 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); + //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"); - replicationState.notifyRefReplicated("someProject", "ref1", uri1, - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); - replicationState.notifyRefReplicated("someProject", "ref2", uri1, - RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + replicationState.notifyRefReplicated( + "someProject", "ref1", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); + replicationState.notifyRefReplicated( + "someProject", "ref2", uri1, RefPushResult.SUCCEEDED, RemoteRefUpdate.Status.OK); replicationState.markAllPushTasksScheduled(); verify(pushResultProcessingMock); } |