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());