Merge branch 'stable-3.12'
* stable-3.12:
Log cancelled fetch collisions without reschedule
Add per-project shared lock to avoid fetch collisions
Revert "Prevent concurrent fetches on the same repository"
Revert "Extract QueueInfo into its own class"
Extract QueueInfo into its own class
Throw LockFailureException from filterAndLock interface
Expose new constructor for LockFailureException
Prevent concurrent fetches on the same repository
Change-Id: I2b8240bb840056d1f836d8a98d651f7115e1d96b
diff --git a/example-setup/broker/Dockerfile b/example-setup/broker/Dockerfile
index e9abc46..980b73a 100644
--- a/example-setup/broker/Dockerfile
+++ b/example-setup/broker/Dockerfile
@@ -18,4 +18,6 @@
COPY --chown=gerrit:gerrit configs/replication.config.template /var/gerrit/etc/
COPY --chown=gerrit:gerrit configs/gerrit.config.template /var/gerrit/etc/
+ENV JAVA_HOME=/usr/lib/jvm/jre-21
+
ENTRYPOINT [ "/tmp/entrypoint.sh" ]
diff --git a/example-setup/http/Dockerfile b/example-setup/http/Dockerfile
index cb4d765..526f37a 100644
--- a/example-setup/http/Dockerfile
+++ b/example-setup/http/Dockerfile
@@ -11,4 +11,6 @@
COPY --chown=gerrit:gerrit configs/replication.config.template /var/gerrit/etc/
COPY --chown=gerrit:gerrit configs/gerrit.config.template /var/gerrit/etc/
+ENV JAVA_HOME=/usr/lib/jvm/jre-21
+
ENTRYPOINT [ "/tmp/entrypoint.sh" ]
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 b94189e..e21aaf3 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
@@ -750,10 +750,10 @@
private Optional<HttpResult> initProject(
Project.NameKey project, URIish uri, FetchApiClient fetchClient, Optional<HttpResult> result)
- throws IOException {
+ throws IOException, URISyntaxException {
+ RevisionReader revisionReader = revReaderProvider.get();
RevisionData refsMetaConfigRevisionData =
- revReaderProvider
- .get()
+ revisionReader
.read(project, null, RefNames.REFS_CONFIG, 0)
.orElseThrow(
() ->
@@ -764,7 +764,12 @@
List<RevisionData> refsMetaConfigDataList =
fetchWholeMetaHistory(project, RefNames.REFS_CONFIG, refsMetaConfigRevisionData);
HttpResult initProjectResult =
- fetchClient.initProject(project, uri, System.currentTimeMillis(), refsMetaConfigDataList);
+ fetchClient.initProject(
+ project,
+ revisionReader.getHeadName(project),
+ uri,
+ System.currentTimeMillis(),
+ refsMetaConfigDataList);
if (initProjectResult.isSuccessful()) {
result = Optional.of(fetchClient.callFetch(project, FetchOne.ALL_REFS, uri));
} else {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java
index 9458083..68fa98a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java
@@ -19,6 +19,7 @@
import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
@@ -319,4 +320,15 @@
return "type:" + type;
}
}
+
+ @Nullable
+ public String getHeadName(Project.NameKey project) throws IOException {
+ try (Repository repo = gitRepositoryManager.openRepository(project)) {
+ Ref ref = repo.exactRef(RefNames.HEAD);
+ if (ref == null) {
+ return null;
+ }
+ return ref.getTarget().getName();
+ }
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
index 3214787..c7e8a8c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
@@ -22,6 +22,7 @@
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
@@ -103,15 +104,17 @@
}
String gitRepositoryName = getGitRepositoryName(httpServletRequest);
+ String headName = getGitRepositoryHeadName(httpServletRequest);
try {
boolean initProjectStatus;
String contentType = httpServletRequest.getContentType();
if (checkContentType(contentType, MediaType.JSON_UTF_8)) {
// init project request includes project configuration in JSON format.
- initProjectStatus = initProjectWithConfiguration(httpServletRequest, gitRepositoryName);
+ initProjectStatus =
+ initProjectWithConfiguration(httpServletRequest, gitRepositoryName, headName);
} else if (checkContentType(contentType, MediaType.PLAIN_TEXT_UTF_8)) {
// init project request does not include project configuration.
- initProjectStatus = initProject(gitRepositoryName);
+ initProjectStatus = initProject(gitRepositoryName, RefNames.HEAD);
} else {
setResponse(
httpServletResponse,
@@ -146,17 +149,17 @@
}
}
- public boolean initProject(String gitRepositoryName)
+ public boolean initProject(String gitRepositoryName, String headName)
throws AuthException, PermissionBackendException, IOException {
- if (initProject(gitRepositoryName, true)) {
- repLog.info("Init project API from {}", gitRepositoryName);
+ if (initProject(gitRepositoryName, headName, true)) {
+ repLog.info("Init project {} with head {}", gitRepositoryName, headName);
return true;
}
return false;
}
private boolean initProjectWithConfiguration(
- HttpServletRequest httpServletRequest, String gitRepositoryName)
+ HttpServletRequest httpServletRequest, String gitRepositoryName, String headName)
throws AuthException,
PermissionBackendException,
IOException,
@@ -167,7 +170,7 @@
RevisionsInput input = PayloadSerDes.parseRevisionsInput(httpServletRequest);
validateInput(input);
- if (!initProject(gitRepositoryName, false)) {
+ if (!initProject(gitRepositoryName, headName, false)) {
return false;
}
@@ -206,7 +209,8 @@
return true;
}
- private boolean initProject(String gitRepositoryName, boolean needsProjectReindexing)
+ private boolean initProject(
+ String gitRepositoryName, String headName, boolean needsProjectReindexing)
throws AuthException, PermissionBackendException, IOException {
// When triggered internally(for example by consuming stream events) user is not provided
// and internal user is returned. Project creation should be always allowed for internal user.
@@ -220,7 +224,7 @@
}
LocalFS localFS = new LocalFS(maybeUri.get());
Project.NameKey projectNameKey = Project.NameKey.parse(gitRepositoryName);
- if (localFS.createProject(projectNameKey, RefNames.HEAD)) {
+ if (localFS.createProject(projectNameKey, headName)) {
if (needsProjectReindexing) {
projectCache.onCreateProject(projectNameKey);
}
@@ -247,6 +251,11 @@
return Url.decode(path.substring(path.lastIndexOf('/') + 1));
}
+ private String getGitRepositoryHeadName(HttpServletRequest httpServletRequest) {
+ String headName = httpServletRequest.getParameter("headName");
+ return MoreObjects.firstNonNull(headName, RefNames.HEAD);
+ }
+
private void logExceptionAndUpdateResponse(
HttpServletResponse httpServletResponse,
Exception e,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
index b908435..67651ff 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchApiClient.java
@@ -16,6 +16,7 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.Project.NameKey;
import com.googlesource.gerrit.plugins.replication.pull.Source;
@@ -23,6 +24,7 @@
import com.googlesource.gerrit.plugins.replication.pull.api.data.BatchApplyObjectData;
import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
import java.io.IOException;
+import java.net.URISyntaxException;
import java.util.List;
import org.eclipse.jgit.transport.URIish;
@@ -68,10 +70,11 @@
*/
HttpResult initProject(
Project.NameKey project,
+ @Nullable String headName,
URIish uri,
long eventCreatedOn,
List<RevisionData> refsMetaConfigRevisionData)
- throws IOException;
+ throws IOException, URISyntaxException;
HttpResult deleteProject(Project.NameKey project, URIish apiUri) throws IOException;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
index c3bb378..ec33a64 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
@@ -44,6 +44,8 @@
import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionsInput;
import com.googlesource.gerrit.plugins.replication.pull.filter.SyncRefsFilter;
import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
@@ -59,6 +61,7 @@
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.message.BasicHeader;
@@ -190,11 +193,12 @@
@Override
public HttpResult initProject(
NameKey project,
+ @Nullable String headName,
URIish uri,
long eventCreatedOn,
List<RevisionData> refsMetaConfigRevisionData)
- throws IOException {
- String url = formatInitProjectUrl(uri.toString(), project);
+ throws IOException, URISyntaxException {
+ URI url = formatInitProjectUrl(uri.toString(), project, headName);
RevisionData[] inputData = new RevisionData[refsMetaConfigRevisionData.size()];
RevisionsInput input =
@@ -321,10 +325,18 @@
targetUri, urlAuthenticationPrefix, Url.encode(project.get()), pluginName, api);
}
- private String formatInitProjectUrl(String targetUri, Project.NameKey project) {
- return String.format(
- "%s/%splugins/%s/init-project/%s.git",
- targetUri, urlAuthenticationPrefix, pluginName, Url.encode(project.get()));
+ private URI formatInitProjectUrl(
+ String targetUri, Project.NameKey project, @Nullable String headName)
+ throws URISyntaxException {
+ String baseUrl =
+ String.format(
+ "%s/%splugins/%s/init-project/%s.git",
+ targetUri, urlAuthenticationPrefix, pluginName, Url.encode(project.get()));
+ URIBuilder baseUriBuilder = new URIBuilder(baseUrl);
+ if (headName != null) {
+ baseUriBuilder = baseUriBuilder.addParameter("headName", headName);
+ }
+ return baseUriBuilder.build();
}
private void requireNull(Object object, String string) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
index eb2d7e0..d8cf100 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
@@ -164,7 +164,8 @@
} else if (event instanceof ProjectCreatedEvent) {
ProjectCreatedEvent projectCreatedEvent = (ProjectCreatedEvent) event;
try {
- projectInitializationAction.initProject(getProjectRepositoryName(projectCreatedEvent));
+ projectInitializationAction.initProject(
+ getProjectRepositoryName(projectCreatedEvent), projectCreatedEvent.headName);
fetchRefsAsync(
FetchOne.ALL_REFS,
projectCreatedEvent.instanceId,
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationHealthCheckIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationHealthCheckIT.java
index 84c8525..95aa8b1 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationHealthCheckIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationHealthCheckIT.java
@@ -197,7 +197,7 @@
waitUntil(
() -> {
- boolean healthCheckPassed = healthcheck.run().result == HealthCheck.Result.PASSED;
+ boolean healthCheckPassed = healthcheck.run().result() == HealthCheck.Result.PASSED;
boolean replicationFinished = hasReplicationFinished();
if (!replicationFinished) {
assertWithMessage("Instance reported healthy while waiting for replication to finish")
@@ -215,12 +215,12 @@
});
if (testStartStopwatch.elapsed(TimeUnit.SECONDS) < TEST_HEALTHCHECK_PERIOD_OF_TIME_SEC) {
- assertThat(healthcheck.run().result).isEqualTo(HealthCheck.Result.FAILED);
+ assertThat(healthcheck.run().result()).isEqualTo(HealthCheck.Result.FAILED);
}
waitUntil(
() -> testStartStopwatch.elapsed(TimeUnit.SECONDS) > TEST_HEALTHCHECK_PERIOD_OF_TIME_SEC);
- assertThat(healthcheck.run().result).isEqualTo(HealthCheck.Result.PASSED);
+ assertThat(healthcheck.run().result()).isEqualTo(HealthCheck.Result.PASSED);
}
private boolean hasReplicationFinished() {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITBase.java
index a6058d3..a73a97e 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationITBase.java
@@ -376,6 +376,7 @@
FetchApiClient client = getInstance(FetchApiClient.Factory.class).create(source);
client.initProject(
projectToCreate,
+ null,
new URIish(source.getApis().get(0)),
System.currentTimeMillis(),
Collections.emptyList());
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 7f4ca4e..88a7ba1 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
@@ -182,7 +182,7 @@
when(fetchRestApiClient.callFetch(any(), anyString(), any(), anyLong(), anyBoolean()))
.thenReturn(fetchHttpResult);
when(fetchRestApiClient.callBatchFetch(any(), any(), any())).thenReturn(batchFetchHttpResult);
- when(fetchRestApiClient.initProject(any(), any(), anyLong(), any()))
+ when(fetchRestApiClient.initProject(any(), any(), any(), anyLong(), any()))
.thenReturn(successfulHttpResult);
when(successfulHttpResult.isSuccessful()).thenReturn(true);
when(httpResult.isSuccessful()).thenReturn(true);
@@ -273,7 +273,7 @@
objectUnderTest.start();
objectUnderTest.onEvent(event);
- verify(fetchRestApiClient).initProject(any(), any(), anyLong(), any());
+ verify(fetchRestApiClient).initProject(any(), any(), any(), anyLong(), any());
}
@Test
@@ -299,7 +299,7 @@
objectUnderTest.start();
objectUnderTest.onEvent(event);
- verify(fetchRestApiClient, never()).initProject(any(), any(), anyLong(), any());
+ verify(fetchRestApiClient, never()).initProject(any(), any(), any(), anyLong(), any());
}
@Test
@@ -316,7 +316,7 @@
objectUnderTest.start();
objectUnderTest.onEvent(event);
- verify(fetchRestApiClient, never()).initProject(any(), any(), anyLong(), any());
+ verify(fetchRestApiClient, never()).initProject(any(), any(), any(), anyLong(), any());
}
@Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
index cfff177..4c756f6 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
@@ -557,8 +557,10 @@
@Test
public void shouldCallInitProjectEndpoint() throws Exception {
+ String headName = RefNames.REFS_HEADS + "main";
objectUnderTest.initProject(
Project.nameKey("test_repo"),
+ headName,
new URIish(api),
eventCreatedOn,
Collections.singletonList(createSampleRevisionData()));
@@ -576,6 +578,7 @@
String.format(
"%s/plugins/pull-replication/init-project/test_repo.git",
urlAuthenticationPrefix()));
+ assertThat(httpPut.getURI().getQuery()).isEqualTo("headName=" + headName);
assertThat(payload).isEqualTo(expectedInitProjectWithConfigPayload);
assertAuthentication(httpPut);
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java
index 980c509..dd6b110 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListenerTest.java
@@ -25,6 +25,7 @@
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.data.RefUpdateAttribute;
import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.events.ProjectCreatedEvent;
@@ -208,10 +209,12 @@
ProjectCreatedEvent event = new ProjectCreatedEvent();
event.instanceId = REMOTE_INSTANCE_ID;
event.projectName = TEST_PROJECT;
+ event.headName = RefNames.HEAD;
objectUnderTest.onEvent(event);
- verify(projectInitializationAction).initProject(String.format("%s.git", TEST_PROJECT));
+ verify(projectInitializationAction)
+ .initProject(String.format("%s.git", TEST_PROJECT), RefNames.HEAD);
}
@Test
@@ -221,10 +224,11 @@
ProjectCreatedEvent event = new ProjectCreatedEvent();
event.instanceId = REMOTE_INSTANCE_ID;
event.projectName = TEST_PROJECT;
+ event.headName = RefNames.HEAD;
objectUnderTest.onEvent(event);
- verify(projectInitializationAction, never()).initProject(any());
+ verify(projectInitializationAction, never()).initProject(any(), eq(RefNames.HEAD));
}
@Test
@@ -234,10 +238,11 @@
ProjectCreatedEvent event = new ProjectCreatedEvent();
event.instanceId = REMOTE_INSTANCE_ID;
event.projectName = TEST_PROJECT;
+ event.headName = RefNames.HEAD;
objectUnderTest.onEvent(event);
- verify(projectInitializationAction, never()).initProject(any());
+ verify(projectInitializationAction, never()).initProject(any(), eq(RefNames.HEAD));
}
@Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/health/PullReplicationTasksHealthCheckTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/health/PullReplicationTasksHealthCheckTest.java
index d06c9ea..f2a090b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/health/PullReplicationTasksHealthCheckTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/health/PullReplicationTasksHealthCheckTest.java
@@ -165,7 +165,7 @@
PullReplicationTasksHealthCheck check =
injector.getInstance(PullReplicationTasksHealthCheck.class);
- assertThat(check.run().result).isEqualTo(HealthCheck.Result.FAILED);
+ assertThat(check.run().result()).isEqualTo(HealthCheck.Result.FAILED);
}
@Test
@@ -177,7 +177,7 @@
PullReplicationTasksHealthCheck check =
injector.getInstance(PullReplicationTasksHealthCheck.class);
- assertThat(check.run().result).isEqualTo(HealthCheck.Result.PASSED);
+ assertThat(check.run().result()).isEqualTo(HealthCheck.Result.PASSED);
}
@Test
@@ -197,7 +197,7 @@
() -> fakeTicker.advance(Duration.ofMillis(periodOfTimeMillis))))
.containsExactly(HealthCheck.Result.FAILED, HealthCheck.Result.PASSED);
- assertThat(check.run().result).isEqualTo(HealthCheck.Result.PASSED);
+ assertThat(check.run().result()).isEqualTo(HealthCheck.Result.PASSED);
}
private Injector testInjector(AbstractModule testModule) {
@@ -222,7 +222,7 @@
int nTimes, PullReplicationTasksHealthCheck check, @Nullable Runnable postRunFunc) {
List<HealthCheck.Result> results = new ArrayList<>();
for (int i = 0; i < nTimes; i++) {
- results.add(check.run().result);
+ results.add(check.run().result());
if (postRunFunc != null) {
postRunFunc.run();
}