Always fallback to fetch when ApplyObject REST-API fails
Previously the Git fetch was invoked only when the ApplyObject
failed because of missing parent commit. However, there are other
cases when the ApplyObject may fail and *must* be covered by
a fallback to Git fetch.
One example of these failures is a forced update of a branch
or a lock failure.
Falling back to a fetch allows to complete the replication anyway
by relying on the Git protocol to resolve the complete fetch.
Change-Id: I2a7b94d0088c65dbade3e3bdc0beae14d46e6410
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
index 431255c..3db867b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
@@ -232,9 +232,19 @@
CallFunction call = getCallFunction(project, objectId, refName, isDelete, state);
return (source) -> {
+ boolean callSuccessful;
try {
- call.call(source);
- } catch (MissingParentObjectException e) {
+ callSuccessful = call.call(source);
+ } catch (Exception e) {
+ repLog.warn(
+ String.format(
+ "Failed to apply object %s on project %s:%s, falling back to git fetch",
+ objectId.name(), project, refName),
+ e);
+ callSuccessful = false;
+ }
+
+ if (!callSuccessful) {
callFetch(source, project, refName, state);
}
};
@@ -274,7 +284,7 @@
return (source) -> callFetch(source, project, refName, state);
}
- private void callSendObject(
+ private boolean callSendObject(
Source source,
Project.NameKey project,
String refName,
@@ -282,6 +292,7 @@
RevisionData revision,
ReplicationState state)
throws MissingParentObjectException {
+ boolean resultIsSuccessful = true;
if (source.wouldFetchProject(project) && source.wouldFetchRef(refName)) {
for (String apiUrl : source.getApis()) {
try {
@@ -316,6 +327,8 @@
project, refName, source.getRemoteConfigName());
}
}
+
+ resultIsSuccessful &= result.isSuccessful();
} catch (URISyntaxException e) {
repLog.warn(
"Pull replication REST API apply object to {} *FAILED* for {}:{} - {}",
@@ -325,6 +338,7 @@
revision,
e);
stateLog.error(String.format("Cannot parse pull replication api url:%s", apiUrl), state);
+ resultIsSuccessful = false;
} catch (IOException e) {
repLog.warn(
"Pull replication REST API apply object to {} *FAILED* for {}:{} - {}",
@@ -340,13 +354,17 @@
apiUrl, e.getMessage()),
e,
state);
+ resultIsSuccessful = false;
}
}
}
+
+ return resultIsSuccessful;
}
- private void callFetch(
+ private boolean callFetch(
Source source, Project.NameKey project, String refName, ReplicationState state) {
+ boolean resultIsSuccessful = true;
if (source.wouldFetchProject(project) && source.wouldFetchRef(refName)) {
for (String apiUrl : source.getApis()) {
try {
@@ -374,8 +392,11 @@
apiUrl, result.getMessage().orElse("unknown")),
state);
}
+
+ resultIsSuccessful &= result.isSuccessful();
} catch (URISyntaxException e) {
stateLog.error(String.format("Cannot parse pull replication api url:%s", apiUrl), state);
+ resultIsSuccessful = false;
} catch (Exception e) {
stateLog.error(
String.format(
@@ -384,9 +405,12 @@
apiUrl, e.getMessage()),
e,
state);
+ resultIsSuccessful = false;
}
}
}
+
+ return resultIsSuccessful;
}
public boolean retry(int attempt, int maxRetries) {
@@ -453,6 +477,6 @@
@FunctionalInterface
private interface CallFunction {
- void call(Source source) throws MissingParentObjectException;
+ boolean call(Source source) throws MissingParentObjectException;
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
index 56b6ad1..b8ff766 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
@@ -80,6 +80,8 @@
@Mock AccountInfo accountInfo;
@Mock RevisionReader revReader;
@Mock RevisionData revisionData;
+ @Mock HttpResult successfulHttpResult;
+ @Mock HttpResult fetchHttpResult;
@Mock HttpResult httpResult;
ApplyObjectMetrics applyObjectMetrics;
FetchReplicationMetrics fetchMetrics;
@@ -110,8 +112,12 @@
when(fetchClientFactory.create(any())).thenReturn(fetchRestApiClient);
when(fetchRestApiClient.callSendObject(any(), anyString(), anyBoolean(), any(), any()))
.thenReturn(httpResult);
- when(fetchRestApiClient.callFetch(any(), anyString(), any(), anyLong())).thenReturn(httpResult);
+ when(fetchRestApiClient.callFetch(any(), anyString(), any(), anyLong()))
+ .thenReturn(fetchHttpResult);
+ when(fetchRestApiClient.initProject(any(), any())).thenReturn(successfulHttpResult);
+ when(successfulHttpResult.isSuccessful()).thenReturn(true);
when(httpResult.isSuccessful()).thenReturn(true);
+ when(fetchHttpResult.isSuccessful()).thenReturn(true);
when(httpResult.isProjectMissing(any())).thenReturn(false);
applyObjectMetrics = new ApplyObjectMetrics("pull-replication", new DisabledMetricMaker());