Merge "Retry failovers by applying url distribution strategy"
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 ecc55a2..2bf2ae4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/OnStartStop.java
@@ -53,7 +53,7 @@
ReplicationState state = new ReplicationState(new GitUpdateProcessing(eventDispatcher));
pushAllFuture.set(
pushAll
- .create(null, Set.of(), ReplicationFilter.all(), state, false)
+ .create(null, PushOne.ALL_REFS, Set.of(), ReplicationFilter.all(), state, false)
.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 8958a2a..19ae420 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushAll.java
@@ -29,7 +29,8 @@
public interface Factory {
PushAll create(
- String urlMatch,
+ @Assisted("urlMatch") String urlMatch,
+ @Assisted("refName") String refName,
Set<String> remotesToConsider,
ReplicationFilter filter,
ReplicationState state,
@@ -41,6 +42,7 @@
private final ReplicationQueue replication;
private final String urlMatch;
private final Set<String> remotesToConsider;
+ private final String refName;
private final ReplicationFilter filter;
private final ReplicationState state;
private final boolean now;
@@ -51,7 +53,8 @@
ProjectCache projectCache,
ReplicationQueue rq,
ReplicationStateListeners stateLog,
- @Assisted @Nullable String urlMatch,
+ @Assisted("urlMatch") @Nullable String urlMatch,
+ @Assisted("refName") String refName,
@Assisted Set<String> remotesToConsider,
@Assisted ReplicationFilter filter,
@Assisted ReplicationState state,
@@ -61,6 +64,7 @@
this.replication = rq;
this.stateLog = stateLog;
this.urlMatch = urlMatch;
+ this.refName = refName;
this.remotesToConsider = remotesToConsider;
this.filter = filter;
this.state = state;
@@ -76,7 +80,7 @@
try {
for (Project.NameKey nameKey : projectCache.all()) {
if (filter.matches(nameKey)) {
- replication.scheduleFullSync(nameKey, urlMatch, remotesToConsider, state, now);
+ replication.scheduleFullSync(nameKey, urlMatch, refName, remotesToConsider, state, now);
}
}
} catch (Exception e) {
@@ -87,7 +91,9 @@
@Override
public String toString() {
- String s = "Replicate All Projects";
+ String refs = PushOne.ALL_REFS.equals(refName) ? "All Refs" : "[" + refName + "]";
+ String s = "Replicate " + refs + " for All Projects";
+
if (urlMatch != null) {
s = s + " to " + urlMatch;
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/RepairCommand.java b/src/main/java/com/googlesource/gerrit/plugins/replication/RepairCommand.java
index 0371c9c..dd5e506 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/RepairCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RepairCommand.java
@@ -110,6 +110,7 @@
"\nRunning replication start for " + project.get() + " to " + uri.toString() + " ...");
replicationStarter.start(
uri.toString(),
+ PushOne.ALL_REFS,
Set.of(),
new ReplicationFilter(List.of(project.get()), Collections.emptyList()),
/* now= */ true,
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 2652cb9..59acb20 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
@@ -130,13 +130,9 @@
}
public void scheduleFullSync(
- Project.NameKey project, String urlMatch, ReplicationState state, boolean now) {
- scheduleFullSync(project, urlMatch, Set.of(), state, now);
- }
-
- public void scheduleFullSync(
Project.NameKey project,
String urlMatch,
+ String refName,
Set<String> remotesToConsider,
ReplicationState state,
boolean now) {
@@ -144,7 +140,7 @@
project,
urlMatch,
remotesToConsider,
- Set.of(new GitReferenceUpdated.UpdatedRef(PushOne.ALL_REFS, null, null, null)),
+ Set.of(new GitReferenceUpdated.UpdatedRef(refName, null, null, null)),
state,
now);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStarter.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStarter.java
index b7baab9..7202eab 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStarter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationStarter.java
@@ -37,6 +37,7 @@
void start(
@Nullable String urlMatch,
+ String refName,
Set<String> remotesToConsider,
ReplicationFilter filter,
boolean now,
@@ -46,7 +47,7 @@
Future<?> future =
pushFactory
- .create(urlMatch, remotesToConsider, filter, state, now)
+ .create(urlMatch, refName, remotesToConsider, filter, state, now)
.schedule(0, TimeUnit.SECONDS);
if (wait) {
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 2c084b1..d6d99a8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/StartCommand.java
@@ -37,6 +37,9 @@
@Option(name = "--url", metaVar = "SUBSTRING", usage = "substring URL must match (or * to match everything)")
private String urlMatch;
+ @Option(name = "--ref", metaVar = "RefName", usage = "ref to replicate")
+ private String refName = PushOne.ALL_REFS;
+
private final Set<String> remotesToConsider = new HashSet<>();
@Option(name = "--remote", metaVar = "REMOTE", usage = "name of remote to replicate to")
@@ -68,7 +71,7 @@
? ReplicationFilter.all()
: new ReplicationFilter(projectPatterns, Collections.emptyList());
- replicationStarter.start(urlMatch, remotesToConsider, projectFilter, now, wait, this);
+ replicationStarter.start(urlMatch, refName, remotesToConsider, projectFilter, now, wait, this);
}
@Override
diff --git a/src/main/resources/Documentation/cmd-start.md b/src/main/resources/Documentation/cmd-start.md
index e6d9fbd..47989a1 100644
--- a/src/main/resources/Documentation/cmd-start.md
+++ b/src/main/resources/Documentation/cmd-start.md
@@ -13,6 +13,7 @@
[--now]
[--wait]
[--remote <REMOTE> ...]
+ [--ref <RefName>]
{--url <PATTERN> | [--url <PATTERN>] --all | [--url <PATTERN>] <PROJECT PATTERN> ...}
```
@@ -97,6 +98,10 @@
`--all`
: Schedule replication for all projects.
+`--ref <RefName>`
+: Replicate only the single reference specified by `<RefName>`.
+If omitted, the command defaults to all references (`refs/*`).
+
`--url <PATTERN>`
: Replicate only to replication destinations whose configuration
URL contains the substring `PATTERN`, or whose expanded project
@@ -131,6 +136,12 @@
$ ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ start tools/gerrit
```
+Replicate only the `master` branch of the `tools/gerrit` project:
+
+```console
+ $ ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ start --ref refs/heads/master tools/gerrit
+```
+
Replicate only projects located in the `documentation` subdirectory:
```console
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
index d436f42..7ff0f3b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationIT.java
@@ -189,6 +189,30 @@
}
@Test
+ public void shouldReplicateOnlySpecificRef() throws Exception {
+ Project.NameKey targetProject = createTestProject(project + "replica");
+
+ setReplicationDestination("foo", "replica", ALL_PROJECTS);
+ reloadConfig();
+
+ String branch1 = "refs/heads/branch1";
+ String branch2 = "refs/heads/branch2";
+ createNewBranchWithoutPush("refs/heads/master", branch1);
+ createNewBranchWithoutPush("refs/heads/master", branch2);
+
+ plugin
+ .getSysInjector()
+ .getInstance(ReplicationQueue.class)
+ .scheduleFullSync(project, null, branch1, Set.of(), new ReplicationState(NO_OP), true);
+
+ try (Repository repo = repoManager.openRepository(targetProject)) {
+ waitUntil(() -> checkedGetRef(repo, branch1) != null);
+ assertThat(getRef(repo, branch1)).isNotNull();
+ assertThat(getRef(repo, branch2)).isNull();
+ }
+ }
+
+ @Test
public void shouldReplicateNewBranchToTwoRemotes() throws Exception {
Project.NameKey targetProject1 = createTestProject(project + "replica1");
Project.NameKey targetProject2 = createTestProject(project + "replica2");
@@ -232,7 +256,7 @@
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
- .scheduleFullSync(project, urlMatch, new ReplicationState(NO_OP), true);
+ .scheduleFullSync(project, urlMatch, PushOne.ALL_REFS, Set.of(), new ReplicationState(NO_OP), true);
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, newRef) != null);
@@ -258,7 +282,7 @@
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
- .scheduleFullSync(project, urlMatch, new ReplicationState(NO_OP), true);
+ .scheduleFullSync(project, urlMatch, PushOne.ALL_REFS, Set.of(), new ReplicationState(NO_OP), true);
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, newRef) != null);
@@ -284,6 +308,7 @@
.getInstance(PushAll.Factory.class)
.create(
null,
+ PushOne.ALL_REFS,
Set.of(),
new ReplicationFilter(Arrays.asList(project.get()), null),
state,
@@ -309,6 +334,7 @@
.getInstance(PushAll.Factory.class)
.create(
null,
+ PushOne.ALL_REFS,
Set.of(),
new ReplicationFilter(Arrays.asList(project.get()), null),
state,
@@ -518,7 +544,7 @@
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
- .scheduleFullSync(project, null, new ReplicationState(NO_OP), true);
+ .scheduleFullSync(project, null, PushOne.ALL_REFS, Set.of(), new ReplicationState(NO_OP), true);
// Wait for the push to land on both the refs
try (Repository r1 = repoManager.openRepository(replica1Project);
@@ -543,7 +569,7 @@
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
- .scheduleFullSync(project, null, new ReplicationState(NO_OP), true);
+ .scheduleFullSync(project, null, PushOne.ALL_REFS, Set.of(), new ReplicationState(NO_OP), true);
// Wait for the push to land in at least one replica
try (Repository r1 = repoManager.openRepository(replica1Project);
@@ -574,7 +600,7 @@
ReplicationQueue queue = plugin.getSysInjector().getInstance(ReplicationQueue.class);
// First sync - goes to replica1 (index 0)
- queue.scheduleFullSync(project, null, new ReplicationState(NO_OP), true);
+ queue.scheduleFullSync(project, null, PushOne.ALL_REFS, Set.of(), new ReplicationState(NO_OP), true);
try (Repository r1 = repoManager.openRepository(replica1Project)) {
waitUntil(() -> checkedGetRef(r1, branch1) != null);
@@ -587,7 +613,7 @@
// Second sync - goes to replica2 (index 1), includes branch1 and branch2
createNewBranchWithoutPush("refs/heads/master", branch2);
- queue.scheduleFullSync(project, null, new ReplicationState(NO_OP), true);
+ queue.scheduleFullSync(project, null, PushOne.ALL_REFS, Set.of(), new ReplicationState(NO_OP), true);
try (Repository r2 = repoManager.openRepository(replica2Project)) {
waitUntil(() -> checkedGetRef(r2, branch1) != null && checkedGetRef(r2, branch2) != null);
@@ -612,7 +638,7 @@
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
- .scheduleFullSync(project, null, Set.of("foo"), new ReplicationState(NO_OP), true);
+ .scheduleFullSync(project, null, PushOne.ALL_REFS, Set.of("foo"), new ReplicationState(NO_OP), true);
try (Repository repo = repoManager.openRepository(targetProject)) {
waitUntil(() -> checkedGetRef(repo, newRef) != null);
@@ -670,10 +696,10 @@
ReplicationQueue replicationQueue = plugin.getSysInjector().getInstance(ReplicationQueue.class);
ReplicationState state = new ReplicationState(NO_OP);
- replicationQueue.scheduleFullSync(prj1, null, Set.of("foo"), state, true);
- replicationQueue.scheduleFullSync(prj2, null, Set.of("foo"), state, true);
- replicationQueue.scheduleFullSync(prj3, null, Set.of("foo"), state, true);
- replicationQueue.scheduleFullSync(prj4, null, Set.of("foo"), state, true);
+ replicationQueue.scheduleFullSync(prj1, null, PushOne.ALL_REFS, Set.of("foo"), state, true);
+ replicationQueue.scheduleFullSync(prj2, null, PushOne.ALL_REFS, Set.of("foo"), state, true);
+ replicationQueue.scheduleFullSync(prj3, null, PushOne.ALL_REFS, Set.of("foo"), state, true);
+ replicationQueue.scheduleFullSync(prj4, null, PushOne.ALL_REFS, Set.of("foo"), state, true);
try (Repository excludeRepo1 = repoManager.openRepository(targetPrj1);
Repository excludeRepo2 = repoManager.openRepository(targetPrj2);
@@ -725,7 +751,8 @@
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
- .scheduleFullSync(project, null, Set.of("bar"), new ReplicationState(NO_OP), true);
+ .scheduleFullSync(
+ project, null, PushOne.ALL_REFS, Set.of("bar"), new ReplicationState(NO_OP), true);
try (Repository repo = repoManager.openRepository(targetProject)) {
assertThrows(
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageIT.java
index 643a781..d0affe2 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/ReplicationStorageIT.java
@@ -76,12 +76,36 @@
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
- .scheduleFullSync(project, null, new ReplicationState(NO_OP), false);
+ .scheduleFullSync(project, null, PushOne.ALL_REFS, Set.of(), new ReplicationState(NO_OP), false);
assertThat(listWaitingReplicationTasks(Pattern.quote(PushOne.ALL_REFS))).hasSize(1);
}
@Test
+ public void shouldCreateOneReplicationTaskWhenSchedulingSpecificRefSync() throws Exception {
+ createTestProject(project + "replica");
+
+ setReplicationDestination("foo", "replica", ALL_PROJECTS, Integer.MAX_VALUE);
+ reloadConfig();
+
+ String specificRef = "refs/heads/master";
+
+ plugin
+ .getSysInjector()
+ .getInstance(ReplicationQueue.class)
+ .scheduleFullSync(project, null, specificRef, Set.of(), new ReplicationState(NO_OP), false);
+
+ assertThat(listWaitingReplicationTasks(Pattern.quote(specificRef))).hasSize(1);
+
+ tasksStorage
+ .streamWaiting()
+ .forEach(
+ (task) -> {
+ assertThat(task.refs()).containsExactly(specificRef);
+ });
+ }
+
+ @Test
public void shouldFirePendingOnlyToIncompleteUri() throws Exception {
String suffix1 = "replica1";
String suffix2 = "replica2";
@@ -197,7 +221,7 @@
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
- .scheduleFullSync(project, urlMatch, new ReplicationState(NO_OP), false);
+ .scheduleFullSync(project, urlMatch, PushOne.ALL_REFS, Set.of(), new ReplicationState(NO_OP), false);
assertThat(listWaiting()).hasSize(1);
tasksStorage
@@ -222,7 +246,7 @@
plugin
.getSysInjector()
.getInstance(ReplicationQueue.class)
- .scheduleFullSync(project, urlMatch, new ReplicationState(NO_OP), false);
+ .scheduleFullSync(project, urlMatch, PushOne.ALL_REFS, Set.of(), new ReplicationState(NO_OP), false);
assertThat(listWaiting()).hasSize(1);
tasksStorage