Handle batch-apply-object requests in the replica Add support for the batch-apply-object in the `PullReplicationFilter` class, which is used by the replica node in a pull-replication setup. Deserialise the json message into the appopriate class, and pass the message for further processing to the relevant "action" class. Bug: Issue 40015567 Change-Id: I069561ed21a9cc07e667590a6afd38b7706858d4
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationEndpoints.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationEndpoints.java index fc97945..253af58 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationEndpoints.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationEndpoints.java
@@ -33,6 +33,8 @@ @UsedAt(PLUGIN_MULTI_SITE) public static final String APPLY_OBJECTS_API_ENDPOINT = "apply-objects"; + public static final String BATCH_APPLY_OBJECT_API_ENDPOINT = "batch-apply-object"; + public static final String FETCH_ENDPOINT = "fetch"; public static final String INIT_PROJECT_ENDPOINT = "init-project"; public static final String DELETE_PROJECT_ENDPOINT = "delete-project";
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java index ed6390a..bfe51a2 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilter.java
@@ -27,6 +27,7 @@ import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; import com.google.common.flogger.FluentLogger; +import com.google.common.reflect.TypeToken; import com.google.gerrit.entities.Project; import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.extensions.api.projects.HeadInput; @@ -63,6 +64,8 @@ import java.io.EOFException; import java.io.IOException; import java.io.PrintWriter; +import java.lang.reflect.Type; +import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; @@ -85,6 +88,7 @@ private FetchAction fetchAction; private ApplyObjectAction applyObjectAction; private ApplyObjectsAction applyObjectsAction; + private BatchApplyObjectAction batchApplyObjectAction; private ProjectInitializationAction projectInitializationAction; private UpdateHeadAction updateHEADAction; private ProjectDeletionAction projectDeletionAction; @@ -98,6 +102,7 @@ FetchAction fetchAction, ApplyObjectAction applyObjectAction, ApplyObjectsAction applyObjectsAction, + BatchApplyObjectAction batchApplyObjectAction, ProjectInitializationAction projectInitializationAction, UpdateHeadAction updateHEADAction, ProjectDeletionAction projectDeletionAction, @@ -107,6 +112,7 @@ this.fetchAction = fetchAction; this.applyObjectAction = applyObjectAction; this.applyObjectsAction = applyObjectsAction; + this.batchApplyObjectAction = batchApplyObjectAction; this.projectInitializationAction = projectInitializationAction; this.updateHEADAction = updateHEADAction; this.projectDeletionAction = projectDeletionAction; @@ -136,6 +142,9 @@ } else if (isApplyObjectsAction(httpRequest)) { failIfcurrentUserIsAnonymous(); writeResponse(httpResponse, doApplyObjects(httpRequest)); + } else if (isBatchApplyObjectsAction(httpRequest)) { + failIfcurrentUserIsAnonymous(); + writeResponse(httpResponse, doBatchApplyObject(httpRequest)); } else if (isInitProjectAction(httpRequest)) { failIfcurrentUserIsAnonymous(); if (!checkAcceptHeader(httpRequest, httpResponse)) { @@ -213,7 +222,7 @@ @SuppressWarnings("unchecked") private Response<String> doApplyObject(HttpServletRequest httpRequest) throws RestApiException, IOException, PermissionBackendException { - RevisionInput input = readJson(httpRequest, TypeLiteral.get(RevisionInput.class)); + RevisionInput input = readJson(httpRequest, TypeLiteral.get(RevisionInput.class).getType()); IdString id = getProjectName(httpRequest).get(); return (Response<String>) applyObjectAction.apply(parseProjectResource(id), input); @@ -222,15 +231,26 @@ @SuppressWarnings("unchecked") private Response<String> doApplyObjects(HttpServletRequest httpRequest) throws RestApiException, IOException, PermissionBackendException { - RevisionsInput input = readJson(httpRequest, TypeLiteral.get(RevisionsInput.class)); + RevisionsInput input = readJson(httpRequest, TypeLiteral.get(RevisionsInput.class).getType()); IdString id = getProjectName(httpRequest).get(); return (Response<String>) applyObjectsAction.apply(parseProjectResource(id), input); } @SuppressWarnings("unchecked") + private Response<Map<String, Object>> doBatchApplyObject(HttpServletRequest httpRequest) + throws RestApiException, IOException, PermissionBackendException { + TypeToken<List<RevisionInput>> collectionType = new TypeToken<>() {}; + List<RevisionInput> inputs = readJson(httpRequest, collectionType.getType()); + IdString id = getProjectName(httpRequest).get(); + + return (Response<Map<String, Object>>) + batchApplyObjectAction.apply(parseProjectResource(id), inputs); + } + + @SuppressWarnings("unchecked") private Response<String> doUpdateHEAD(HttpServletRequest httpRequest) throws Exception { - HeadInput input = readJson(httpRequest, TypeLiteral.get(HeadInput.class)); + HeadInput input = readJson(httpRequest, TypeLiteral.get(HeadInput.class).getType()); IdString id = getProjectName(httpRequest).get(); return (Response<String>) updateHEADAction.apply(parseProjectResource(id), input); @@ -247,7 +267,7 @@ @SuppressWarnings("unchecked") private Response<Map<String, Object>> doFetch(HttpServletRequest httpRequest) throws IOException, RestApiException, PermissionBackendException { - Input input = readJson(httpRequest, TypeLiteral.get(Input.class)); + Input input = readJson(httpRequest, TypeLiteral.get(Input.class).getType()); IdString id = getProjectName(httpRequest).get(); return (Response<Map<String, Object>>) fetchAction.apply(parseProjectResource(id), input); @@ -276,7 +296,7 @@ } } - private <T> T readJson(HttpServletRequest httpRequest, TypeLiteral<T> typeLiteral) + private <T> T readJson(HttpServletRequest httpRequest, Type typeToken) throws IOException, BadRequestException { try (BufferedReader br = httpRequest.getReader(); @@ -290,7 +310,7 @@ throw new BadRequestException("Expected JSON object", e); } - return gson.fromJson(json, typeLiteral.getType()); + return gson.fromJson(json, typeToken); } finally { try { // Reader.close won't consume the rest of the input. Explicitly consume the request @@ -343,6 +363,12 @@ .endsWith(String.format("/%s~" + APPLY_OBJECTS_API_ENDPOINT, pluginName)); } + private boolean isBatchApplyObjectsAction(HttpServletRequest httpRequest) { + return httpRequest + .getRequestURI() + .endsWith(String.format("/%s~" + BATCH_APPLY_OBJECT_API_ENDPOINT, pluginName)); + } + private boolean isFetchAction(HttpServletRequest httpRequest) { return httpRequest.getRequestURI().endsWith(String.format("/%s~" + FETCH_ENDPOINT, pluginName)); }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilterTest.java index f767af4..850d04a 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilterTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationFilterTest.java
@@ -9,7 +9,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.atLeastOnce; -import static org.mockito.internal.verification.VerificationModeFactory.times; import com.google.common.net.MediaType; import com.google.gerrit.entities.Project; @@ -42,6 +41,7 @@ @Mock private FetchAction fetchAction; @Mock private ApplyObjectAction applyObjectAction; @Mock private ApplyObjectsAction applyObjectsAction; + @Mock private BatchApplyObjectAction batchApplyObjectAction; @Mock private ProjectInitializationAction projectInitializationAction; @Mock private UpdateHeadAction updateHEADAction; @Mock private ProjectDeletionAction projectDeletionAction; @@ -62,6 +62,9 @@ String.format("any-prefix/projects/%s/%s~apply-objects", PROJECT_NAME, PLUGIN_NAME); private final String HEAD_URI = String.format("any-prefix/projects/%s/%s~HEAD", PROJECT_NAME, PLUGIN_NAME); + + private final String BATCH_APPLY_OBJECT_URI = + String.format("any-prefix/projects/%s/%s~batch-apply-object", PROJECT_NAME, PLUGIN_NAME); private final String DELETE_PROJECT_URI = String.format("any-prefix/projects/%s/%s~delete-project", PROJECT_NAME, PLUGIN_NAME); private final String INIT_PROJECT_URI = @@ -78,6 +81,7 @@ fetchAction, applyObjectAction, applyObjectsAction, + batchApplyObjectAction, projectInitializationAction, updateHEADAction, projectDeletionAction, @@ -180,7 +184,6 @@ final PullReplicationFilter pullReplicationFilter = createPullReplicationFilter(); pullReplicationFilter.doFilter(request, response, filterChain); - verify(request, times(5)).getRequestURI(); verify(projectInitializationAction).initProject(eq(PROJECT_NAME_GIT)); verify(response).getWriter(); } @@ -211,7 +214,6 @@ final PullReplicationFilter pullReplicationFilter = createPullReplicationFilter(); pullReplicationFilter.doFilter(request, response, filterChain); - verify(request, times(7)).getRequestURI(); verify(projectCache).get(Project.nameKey(PROJECT_NAME)); verify(projectDeletionAction).apply(any(ProjectResource.class), any()); verify(response).getWriter(); @@ -364,4 +366,33 @@ verify(response).setStatus(HttpServletResponse.SC_BAD_REQUEST); } + + @Test + public void shouldFilterBatchApplyObjectAction() throws Exception { + + byte[] payloadApplyObject = + ("[{\"label\":\"Replication\",\"ref_name\":\"refs/heads/foo\"," + + "\"revision_data\":{" + + "\"commit_object\":{\"type\":1,\"content\":\"some-content\"}," + + "\"tree_object\":{\"type\":2,\"content\":\"some-content\"}," + + "\"blobs\":[]}" + + "}," + + "{\"label\":\"Replication\",\"ref_name\":\"refs/heads/bar\"," + + "\"revision_data\":{" + + "\"commit_object\":{\"type\":1,\"content\":\"some-content\"}," + + "\"tree_object\":{\"type\":2,\"content\":\"some-content\"}," + + "\"blobs\":[]}" + + "}]") + .getBytes(StandardCharsets.UTF_8); + + defineBehaviours(payloadApplyObject, BATCH_APPLY_OBJECT_URI); + + when(batchApplyObjectAction.apply(any(), any())).thenReturn(OK_RESPONSE); + + PullReplicationFilter pullReplicationFilter = createPullReplicationFilter(); + pullReplicationFilter.doFilter(request, response, filterChain); + + verifyBehaviours(); + verify(batchApplyObjectAction).apply(any(ProjectResource.class), any()); + } }