From 6a3d6a19592daf7484e1268beda53bb6f6250082 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Sat, 24 Oct 2026 03:37:26 +0530 Subject: [PATCH 01/18] cl --- changelog/unreleased/SOLR-16398-replication-phase3.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 changelog/unreleased/SOLR-16398-replication-phase3.yml diff --git a/changelog/unreleased/SOLR-16398-replication-phase3.yml b/changelog/unreleased/SOLR-16398-replication-phase3.yml new file mode 100644 index 000000000000..0f23f9a1fd1f --- /dev/null +++ b/changelog/unreleased/SOLR-16398-replication-phase3.yml @@ -0,0 +1,9 @@ +title: + Replication Phase 3 - TBA +type: +authors: + - name: Jalaz Kumar + nick: jaykay12 +links: + - name: SOLR-16398 + url: https://issues.apache.org/jira/browse/SOLR-16398 From 5e01f4740807322ef575370017e26bd934efad9e Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Sat, 24 Oct 2026 16:49:29 +0530 Subject: [PATCH 02/18] initial changes --- .../unreleased/SOLR-16398-replication-phase3.yml | 4 ++-- .../client/api/endpoint/ReplicationApis.java | 14 ++++++++++++++ .../api/model/ReplicationEnabledRequestBody.java | 15 +++++++++++++++ .../apache/solr/handler/ReplicationHandler.java | 16 ++++++++++++---- .../solr/handler/admin/api/CoreReplication.java | 9 +++++++++ .../handler/admin/api/ReplicationAPIBase.java | 10 ++++++++++ 6 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java diff --git a/changelog/unreleased/SOLR-16398-replication-phase3.yml b/changelog/unreleased/SOLR-16398-replication-phase3.yml index 0f23f9a1fd1f..308bf36dd4f5 100644 --- a/changelog/unreleased/SOLR-16398-replication-phase3.yml +++ b/changelog/unreleased/SOLR-16398-replication-phase3.yml @@ -1,6 +1,6 @@ title: - Replication Phase 3 - TBA -type: + Add V2 JAX-RS API to enable or disable replication +type: added authors: - name: Jalaz Kumar nick: jaykay12 diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java index f95335dc407a..4c83eb9542dd 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java @@ -22,8 +22,10 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.extensions.Extension; import io.swagger.v3.oas.annotations.extensions.ExtensionProperty; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; +import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.QueryParam; @@ -32,6 +34,8 @@ import org.apache.solr.client.api.model.FileListResponse; import org.apache.solr.client.api.model.IndexVersionResponse; import org.apache.solr.client.api.model.ReplicationDetailsResponse; +import org.apache.solr.client.api.model.ReplicationEnabledRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; import org.apache.solr.client.api.util.CoreApiParameters; @Path("/cores/{coreName}/replication") @@ -106,4 +110,14 @@ StreamingOutput fetchFile( @Parameter(description = "The generation number of the index", required = false) @QueryParam("generation") Long gen); + + @PUT + @CoreApiParameters + @Operation( + summary = "Enabled or disable replication.", + tags = {"replication"}) + SolrJerseyResponse setReplicationEnabled( + @RequestBody(required = true) + ReplicationEnabledRequestBody requestBody); + } diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java new file mode 100644 index 000000000000..e7510504ae5d --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java @@ -0,0 +1,15 @@ +package org.apache.solr.client.api.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ReplicationEnabledRequestBody { + + public ReplicationEnabledRequestBody() {} + + public ReplicationEnabledRequestBody(boolean enabled) { + this.enabled = enabled; + } + + @JsonProperty(required = true) + public boolean enabled; +} diff --git a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java index 8287459f28f3..eb827e29e0f6 100644 --- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java @@ -82,6 +82,7 @@ import org.apache.solr.client.api.model.ReplicationDetailsResponse.FollowerDetails; import org.apache.solr.client.api.model.ReplicationDetailsResponse.LeaderDetails; import org.apache.solr.client.api.model.ReplicationDetailsResponse.ReplicationDetails; +import org.apache.solr.client.api.model.ReplicationEnabledRequestBody; import org.apache.solr.client.api.model.SolrJerseyResponse; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException.ErrorCode; @@ -338,11 +339,9 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw true, true); } else if (CMD_ENABLE_REPL.equalsIgnoreCase(command)) { - replicationEnabled.set(true); - rsp.add(STATUS, OK_STATUS); + setReplicationEnabled(req, rsp, true); } else if (CMD_DISABLE_REPL.equalsIgnoreCase(command)) { - replicationEnabled.set(false); - rsp.add(STATUS, OK_STATUS); + setReplicationEnabled(req, rsp, false); } } @@ -1027,6 +1026,15 @@ public void initializeMetrics(SolrMetricsContext parentContext, Attributes attri downloadSpeedMetric); } + public void setReplicationEnabled(boolean enabled) { + replicationEnabled.set(enabled); + } + + private void setReplicationEnabled(SolrQueryRequest req, SolrQueryResponse rsp, boolean enabled) { + new CoreReplication(core, req, rsp).setReplicationEnabled(new ReplicationEnabledRequestBody(enabled)); + rsp.add(STATUS, OK_STATUS); + } + // TODO Should a failure retrieving any piece of info mark the overall request as a failure? Is // there a core set of values that are required to make a response here useful? /** Used for showing statistics and progress information. */ diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java index 9985eb05a676..1e705dfdcd70 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java @@ -17,6 +17,7 @@ package org.apache.solr.handler.admin.api; import static org.apache.solr.security.PermissionNameProvider.Name.CORE_READ_PERM; +import static org.apache.solr.security.PermissionNameProvider.Name.UPDATE_PERM; import jakarta.inject.Inject; import jakarta.ws.rs.core.StreamingOutput; @@ -25,6 +26,8 @@ import org.apache.solr.client.api.model.FileListResponse; import org.apache.solr.client.api.model.IndexVersionResponse; import org.apache.solr.client.api.model.ReplicationDetailsResponse; +import org.apache.solr.client.api.model.ReplicationEnabledRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; import org.apache.solr.common.SolrException; import org.apache.solr.core.SolrCore; import org.apache.solr.jersey.PermissionName; @@ -80,4 +83,10 @@ public StreamingOutput fetchFile( return doFetchFile( filePath, dirType, offset, len, compression, checksum, maxWriteMBPerSec, gen); } + + @Override + @PermissionName(UPDATE_PERM) + public SolrJerseyResponse setReplicationEnabled(ReplicationEnabledRequestBody requestBody) { + return null; + } } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ReplicationAPIBase.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ReplicationAPIBase.java index 378c5699d31c..785b01ab97a6 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/ReplicationAPIBase.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ReplicationAPIBase.java @@ -49,6 +49,8 @@ import org.apache.solr.client.api.model.FileMetaData; import org.apache.solr.client.api.model.IndexVersionResponse; import org.apache.solr.client.api.model.ReplicationDetailsResponse; +import org.apache.solr.client.api.model.ReplicationEnabledRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; import org.apache.solr.common.SolrException; import org.apache.solr.common.util.FastOutputStream; import org.apache.solr.common.util.IOUtils; @@ -100,6 +102,14 @@ protected ReplicationDetailsResponse doFetchReplicationDetails(boolean showFollo return replicationHandler.getReplicationDetails(showFollowerDetails); } + protected SolrJerseyResponse setReplicationEnabled(ReplicationEnabledRequestBody requestBody) { + ensureRequiredRequestBodyProvided(requestBody); + ensureRequiredParameterProvided("enabled", requestBody.enabled); + ReplicationHandler replicationHandler = (ReplicationHandler) solrCore.getRequestHandler(ReplicationHandler.PATH); + replicationHandler; + return instantiateJerseyResponse(SolrJerseyResponse.class); + } + protected IndexVersionResponse doFetchIndexVersion() throws IOException { ReplicationHandler replicationHandler = (ReplicationHandler) solrCore.getRequestHandler(ReplicationHandler.PATH); From 78485af58af62a937f210194765e177c14064529 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Sat, 24 Oct 2026 16:58:38 +0530 Subject: [PATCH 03/18] tidy up --- ...ml => SOLR-16398-enable-disable-replication.yml} | 0 .../solr/client/api/endpoint/ReplicationApis.java | 4 +--- .../org/apache/solr/handler/ReplicationHandler.java | 13 ++++++------- .../solr/handler/admin/api/CoreReplication.java | 2 +- .../solr/handler/admin/api/ReplicationAPIBase.java | 7 ++++--- 5 files changed, 12 insertions(+), 14 deletions(-) rename changelog/unreleased/{SOLR-16398-replication-phase3.yml => SOLR-16398-enable-disable-replication.yml} (100%) diff --git a/changelog/unreleased/SOLR-16398-replication-phase3.yml b/changelog/unreleased/SOLR-16398-enable-disable-replication.yml similarity index 100% rename from changelog/unreleased/SOLR-16398-replication-phase3.yml rename to changelog/unreleased/SOLR-16398-enable-disable-replication.yml diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java index 4c83eb9542dd..0c570cc53afc 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java @@ -117,7 +117,5 @@ StreamingOutput fetchFile( summary = "Enabled or disable replication.", tags = {"replication"}) SolrJerseyResponse setReplicationEnabled( - @RequestBody(required = true) - ReplicationEnabledRequestBody requestBody); - + @RequestBody(required = true) ReplicationEnabledRequestBody requestBody); } diff --git a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java index eb827e29e0f6..e29fabee34c3 100644 --- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java @@ -339,9 +339,13 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw true, true); } else if (CMD_ENABLE_REPL.equalsIgnoreCase(command)) { - setReplicationEnabled(req, rsp, true); + new CoreReplication(core, req, rsp) + .setReplicationEnabled(new ReplicationEnabledRequestBody(true)); + rsp.add(STATUS, OK_STATUS); } else if (CMD_DISABLE_REPL.equalsIgnoreCase(command)) { - setReplicationEnabled(req, rsp, false); + new CoreReplication(core, req, rsp) + .setReplicationEnabled(new ReplicationEnabledRequestBody(false)); + rsp.add(STATUS, OK_STATUS); } } @@ -1030,11 +1034,6 @@ public void setReplicationEnabled(boolean enabled) { replicationEnabled.set(enabled); } - private void setReplicationEnabled(SolrQueryRequest req, SolrQueryResponse rsp, boolean enabled) { - new CoreReplication(core, req, rsp).setReplicationEnabled(new ReplicationEnabledRequestBody(enabled)); - rsp.add(STATUS, OK_STATUS); - } - // TODO Should a failure retrieving any piece of info mark the overall request as a failure? Is // there a core set of values that are required to make a response here useful? /** Used for showing statistics and progress information. */ diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java index 1e705dfdcd70..64ea0f26f43b 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java @@ -87,6 +87,6 @@ public StreamingOutput fetchFile( @Override @PermissionName(UPDATE_PERM) public SolrJerseyResponse setReplicationEnabled(ReplicationEnabledRequestBody requestBody) { - return null; + return doSetReplicationEnabled(requestBody); } } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ReplicationAPIBase.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ReplicationAPIBase.java index 785b01ab97a6..c38fec9f6d80 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/ReplicationAPIBase.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ReplicationAPIBase.java @@ -102,11 +102,12 @@ protected ReplicationDetailsResponse doFetchReplicationDetails(boolean showFollo return replicationHandler.getReplicationDetails(showFollowerDetails); } - protected SolrJerseyResponse setReplicationEnabled(ReplicationEnabledRequestBody requestBody) { + protected SolrJerseyResponse doSetReplicationEnabled(ReplicationEnabledRequestBody requestBody) { ensureRequiredRequestBodyProvided(requestBody); ensureRequiredParameterProvided("enabled", requestBody.enabled); - ReplicationHandler replicationHandler = (ReplicationHandler) solrCore.getRequestHandler(ReplicationHandler.PATH); - replicationHandler; + ReplicationHandler replicationHandler = + (ReplicationHandler) solrCore.getRequestHandler(ReplicationHandler.PATH); + replicationHandler.setReplicationEnabled(requestBody.enabled); return instantiateJerseyResponse(SolrJerseyResponse.class); } From eff1e8f828bf8d4593c574fa462fdf37c9aad8a9 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Sat, 24 Oct 2026 16:59:05 +0530 Subject: [PATCH 04/18] license --- .../api/model/ReplicationEnabledRequestBody.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java index e7510504ae5d..2c25fbfb21e4 100644 --- a/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java +++ b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.apache.solr.client.api.model; import com.fasterxml.jackson.annotation.JsonProperty; From 89d37e0068dcf0ac1f86e8d6539886cbd0e9a677 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Sat, 24 Oct 2026 17:07:45 +0530 Subject: [PATCH 05/18] local agent review comments --- .../client/api/model/ReplicationEnabledRequestBody.java | 5 ++++- .../java/org/apache/solr/handler/ReplicationHandler.java | 7 ++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java index 2c25fbfb21e4..d485c3c6d28d 100644 --- a/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java +++ b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java @@ -18,6 +18,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; +/** + * Request body for enabling and disabling index replication from leader to the followers + */ public class ReplicationEnabledRequestBody { public ReplicationEnabledRequestBody() {} @@ -27,5 +30,5 @@ public ReplicationEnabledRequestBody(boolean enabled) { } @JsonProperty(required = true) - public boolean enabled; + public Boolean enabled; } diff --git a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java index e29fabee34c3..91872761f28d 100644 --- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java @@ -82,7 +82,6 @@ import org.apache.solr.client.api.model.ReplicationDetailsResponse.FollowerDetails; import org.apache.solr.client.api.model.ReplicationDetailsResponse.LeaderDetails; import org.apache.solr.client.api.model.ReplicationDetailsResponse.ReplicationDetails; -import org.apache.solr.client.api.model.ReplicationEnabledRequestBody; import org.apache.solr.client.api.model.SolrJerseyResponse; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException.ErrorCode; @@ -339,12 +338,10 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw true, true); } else if (CMD_ENABLE_REPL.equalsIgnoreCase(command)) { - new CoreReplication(core, req, rsp) - .setReplicationEnabled(new ReplicationEnabledRequestBody(true)); + setReplicationEnabled(true); rsp.add(STATUS, OK_STATUS); } else if (CMD_DISABLE_REPL.equalsIgnoreCase(command)) { - new CoreReplication(core, req, rsp) - .setReplicationEnabled(new ReplicationEnabledRequestBody(false)); + setReplicationEnabled(false); rsp.add(STATUS, OK_STATUS); } } From be90152ea25f9f7872e04cd595303d422b16b782 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Sat, 24 Oct 2026 17:09:27 +0530 Subject: [PATCH 06/18] typo --- .../org/apache/solr/client/api/endpoint/ReplicationApis.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java index 0c570cc53afc..b460d1c645a8 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplicationApis.java @@ -114,7 +114,7 @@ StreamingOutput fetchFile( @PUT @CoreApiParameters @Operation( - summary = "Enabled or disable replication.", + summary = "Enable or disable replication.", tags = {"replication"}) SolrJerseyResponse setReplicationEnabled( @RequestBody(required = true) ReplicationEnabledRequestBody requestBody); From e6113dc093a996932a5986e3cf116a178f450df7 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Sat, 24 Oct 2026 17:10:54 +0530 Subject: [PATCH 07/18] format --- .../api/model/ReplicationEnabledRequestBody.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java index d485c3c6d28d..0b7d64a47747 100644 --- a/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java +++ b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationEnabledRequestBody.java @@ -18,17 +18,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Request body for enabling and disabling index replication from leader to the followers - */ +/** Request body for enabling and disabling index replication from leader to followers. */ public class ReplicationEnabledRequestBody { - public ReplicationEnabledRequestBody() {} - - public ReplicationEnabledRequestBody(boolean enabled) { - this.enabled = enabled; - } - @JsonProperty(required = true) public Boolean enabled; } From b5828991f245bf52d9ab4309bc0dfb9749c75c3a Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Mon, 26 Oct 2026 02:11:17 +0530 Subject: [PATCH 08/18] added tests --- .../admin/api/CoreReplicationAPITest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/CoreReplicationAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/CoreReplicationAPITest.java index 8406f8db35bf..198bff65c4bd 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/CoreReplicationAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/CoreReplicationAPITest.java @@ -35,6 +35,7 @@ import org.apache.solr.client.api.model.IndexVersionResponse; import org.apache.solr.client.api.model.ReplicationDetailsResponse; import org.apache.solr.client.api.model.ReplicationDetailsResponse.ReplicationDetails; +import org.apache.solr.client.api.model.ReplicationEnabledRequestBody; import org.apache.solr.common.SolrException; import org.apache.solr.core.SolrCore; import org.apache.solr.core.SolrResourceLoader; @@ -100,6 +101,28 @@ public void testFetchReplicationDetails() { assertSame(expectedResponse, withFollowerFalse); } + @Test + public void testSetReplicationEnabled() { + for (boolean enabled : new boolean[] {true, false}) { + final var body = new ReplicationEnabledRequestBody(); + body.enabled = enabled; + assertNotNull(coreReplicationAPI.setReplicationEnabled(body)); + } + } + + @Test + public void testSetReplicationEnabledRejectsNullBody() { + SolrException ex = expectThrows(SolrException.class, () -> coreReplicationAPI.setReplicationEnabled(null)); + assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); + } + + @Test + public void testSetReplicationEnabledRejectsBadBody() { + final var emptyBody = new ReplicationEnabledRequestBody(); + SolrException ex = expectThrows(SolrException.class, () -> coreReplicationAPI.setReplicationEnabled(emptyBody)); + assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); + } + @Test public void testGetIndexVersion() throws Exception { IndexVersionResponse expected = new IndexVersionResponse(123L, 123L, "testGeneration"); From 09f741f5afc7a4900ba58873f3a08b24f725f82c Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Mon, 26 Oct 2026 09:23:21 +0530 Subject: [PATCH 09/18] tidying --- .../solr/handler/admin/api/CoreReplicationAPITest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/CoreReplicationAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/CoreReplicationAPITest.java index 198bff65c4bd..be47f18721c5 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/CoreReplicationAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/CoreReplicationAPITest.java @@ -112,14 +112,17 @@ public void testSetReplicationEnabled() { @Test public void testSetReplicationEnabledRejectsNullBody() { - SolrException ex = expectThrows(SolrException.class, () -> coreReplicationAPI.setReplicationEnabled(null)); + SolrException ex = + expectThrows(SolrException.class, () -> coreReplicationAPI.setReplicationEnabled(null)); assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); } @Test public void testSetReplicationEnabledRejectsBadBody() { final var emptyBody = new ReplicationEnabledRequestBody(); - SolrException ex = expectThrows(SolrException.class, () -> coreReplicationAPI.setReplicationEnabled(emptyBody)); + SolrException ex = + expectThrows( + SolrException.class, () -> coreReplicationAPI.setReplicationEnabled(emptyBody)); assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); } From f7263729baf016f35fa82e67ffc7e23ec3f149bf Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Mon, 26 Oct 2026 09:46:30 +0530 Subject: [PATCH 10/18] more tests --- .../api/SetReplicationEnabledAPITest.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java new file mode 100644 index 000000000000..1936a02da5f9 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.handler.admin.api; + +import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; + +import org.apache.solr.SolrTestCase; +import org.apache.solr.client.solrj.request.ReplicationApi; +import org.apache.solr.common.SolrException; +import org.apache.solr.util.ExternalPaths; +import org.apache.solr.util.SolrJettyTestRule; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +/** HTTP tests for {@code PUT /api/cores/coreName/replication} via the generated SolrJ request. */ +public class SetReplicationEnabledAPITest extends SolrTestCase { + + private static final String CORE_NAME = "setReplicationEnabledApiTestCore"; + + @ClassRule + public static final SolrJettyTestRule solrTestRule = new SolrJettyTestRule(); + + @BeforeClass + public static void setupSolr() throws Exception { + System.setProperty(ALLOW_PATHS_SYSPROP, ExternalPaths.SERVER_HOME.toAbsolutePath().toString()); + solrTestRule.startSolr(createTempDir()); + solrTestRule.newCollection(CORE_NAME).withConfigSet(ExternalPaths.DEFAULT_CONFIGSET).create(); + } + + @Test + public void testDisableThenEnableReplication() throws Exception { + assertEquals(Boolean.TRUE, fetchReplicationEnabledStatus()); + + setReplicationEnabled(false); + assertEquals(Boolean.FALSE, fetchReplicationEnabledStatus()); + + setReplicationEnabled(true); + assertEquals(Boolean.TRUE, fetchReplicationEnabledStatus()); + } + + @Test + public void testMissingEnabledIsRejectedAndStateUnchanged() throws Exception { + assertEquals(Boolean.TRUE, fetchReplicationEnabledStatus()); + + var request = new ReplicationApi.SetReplicationEnabled(CORE_NAME); + SolrException ex = + expectThrows( + SolrException.class, () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); + assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); + + assertEquals(Boolean.TRUE, fetchReplicationEnabledStatus()); + } + + private static void setReplicationEnabled(boolean enabled) throws Exception { + var request = new ReplicationApi.SetReplicationEnabled(CORE_NAME); + request.setEnabled(enabled); + var response = request.process(solrTestRule.getSolrClient(CORE_NAME)); + assertNull(response.error); + } + + private static Boolean fetchReplicationEnabledStatus() throws Exception { + var request = new ReplicationApi.FetchReplicationDetails(CORE_NAME); + return request.process(solrTestRule.getSolrClient(CORE_NAME)).details.leader.replicationEnabled; + } +} From 4573a75fe6de7473df3fa2f824e5904ee774dc96 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Mon, 26 Oct 2026 09:50:31 +0530 Subject: [PATCH 11/18] tidy --- .../solr/handler/admin/api/SetReplicationEnabledAPITest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java index 1936a02da5f9..6ed17b975a37 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java @@ -32,8 +32,7 @@ public class SetReplicationEnabledAPITest extends SolrTestCase { private static final String CORE_NAME = "setReplicationEnabledApiTestCore"; - @ClassRule - public static final SolrJettyTestRule solrTestRule = new SolrJettyTestRule(); + @ClassRule public static final SolrJettyTestRule solrTestRule = new SolrJettyTestRule(); @BeforeClass public static void setupSolr() throws Exception { From 303acb8215fc97e5023e0276fcc7d8f6d3451563 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Mon, 26 Oct 2026 10:08:20 +0530 Subject: [PATCH 12/18] added tests --- .../admin/api/SetReplicationEnabledAPITest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java index 6ed17b975a37..0e68711cae5d 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java @@ -65,6 +65,20 @@ public void testMissingEnabledIsRejectedAndStateUnchanged() throws Exception { assertEquals(Boolean.TRUE, fetchReplicationEnabledStatus()); } + @Test + public void testNullEnabledIsRejectedAndStateUnchanged() throws Exception { + assertEquals(Boolean.TRUE, fetchReplicationEnabledStatus()); + + var request = new ReplicationApi.SetReplicationEnabled(CORE_NAME); + request.setEnabled(null); + SolrException ex = + expectThrows( + SolrException.class, () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); + assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); + + assertEquals(Boolean.TRUE, fetchReplicationEnabledStatus()); + } + private static void setReplicationEnabled(boolean enabled) throws Exception { var request = new ReplicationApi.SetReplicationEnabled(CORE_NAME); request.setEnabled(enabled); From cb3f41ecde0e3f4deec75aa1f0d3983d930c4c73 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Mon, 26 Oct 2026 10:14:59 +0530 Subject: [PATCH 13/18] ref guides --- .../pages/user-managed-index-replication.adoc | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/user-managed-index-replication.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/user-managed-index-replication.adoc index b3e8484098e3..4a0d76b34663 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/user-managed-index-replication.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/user-managed-index-replication.adoc @@ -367,15 +367,52 @@ You can use the HTTP commands below to control the ReplicationHandler's operatio `enablereplication`:: Enable replication on the "leader" for all its followers. + +[tabs#enablereplication-request] +====== +V1 API:: ++ +==== [source,bash] +---- http://{leaderHost}:{port}/solr/{coreName}/replication?command=enablereplication +---- +==== + +V2 API:: ++ +==== +[source,bash] +---- +curl -X PUT -H 'Content-Type: application/json' -d '{"enabled": true}' \ +http://{leaderHost}:{port}/api/cores/{coreName}/replication +---- +==== +====== `disablereplication`:: Disable replication on the leader for all its followers. + +[tabs#disablereplication-request] +====== +V1 API:: ++ +==== [source,bash] +---- http://{leaderHost}:{port}/solr/{coreName}/replication?command=disablereplication +---- +==== +V2 API:: ++ +==== +[source,bash] +---- +curl -X PUT -H 'Content-Type: application/json' -d '{"enabled": false}' \ +http://{leaderHost}:{port}/api/cores/{coreName}/replication +---- +==== +====== `indexversion`:: Return the version of the latest replicatable index on the specified leader or follower. + From 6e1240cd572591e9fa7ee9f54bbe81c00076dffe Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Mon, 26 Oct 2026 10:17:15 +0530 Subject: [PATCH 14/18] minor --- .../deployment-guide/pages/user-managed-index-replication.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/user-managed-index-replication.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/user-managed-index-replication.adoc index 4a0d76b34663..1c20b7919b9e 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/user-managed-index-replication.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/user-managed-index-replication.adoc @@ -413,6 +413,7 @@ http://{leaderHost}:{port}/api/cores/{coreName}/replication ---- ==== ====== + `indexversion`:: Return the version of the latest replicatable index on the specified leader or follower. + From bc0f3221b20abf16b5ebe1a49eb58748a228bd4d Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 26 Oct 2026 06:58:11 -0400 Subject: [PATCH 15/18] SOLR-16398: fix permission for enable/disable replication API PUT /api/cores/coreName/replication used UPDATE_PERM, the generic document-indexing permission, instead of CORE_EDIT_PERM used by every other mutating core-admin v2 API (ReloadCore, RenameCore, UnloadCore, etc). A client granted only 'update' could toggle replication without core-admin rights, while 'core-admin-edit' alone wouldn't suffice. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01RP73fkX7r2Si2w3VejPfxv --- .../unreleased/SOLR-16398-enable-disable-replication.yml | 2 +- .../org/apache/solr/handler/admin/api/CoreReplication.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog/unreleased/SOLR-16398-enable-disable-replication.yml b/changelog/unreleased/SOLR-16398-enable-disable-replication.yml index 308bf36dd4f5..f270aded09be 100644 --- a/changelog/unreleased/SOLR-16398-enable-disable-replication.yml +++ b/changelog/unreleased/SOLR-16398-enable-disable-replication.yml @@ -1,5 +1,5 @@ title: - Add V2 JAX-RS API to enable or disable replication + Add V2 JAX-RS API to enable or disable replication (PUT /api/cores/coreName/replication, requires the core-admin-edit permission) type: added authors: - name: Jalaz Kumar diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java index 64ea0f26f43b..288897e4d31a 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplication.java @@ -16,8 +16,8 @@ */ package org.apache.solr.handler.admin.api; +import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; import static org.apache.solr.security.PermissionNameProvider.Name.CORE_READ_PERM; -import static org.apache.solr.security.PermissionNameProvider.Name.UPDATE_PERM; import jakarta.inject.Inject; import jakarta.ws.rs.core.StreamingOutput; @@ -85,7 +85,7 @@ public StreamingOutput fetchFile( } @Override - @PermissionName(UPDATE_PERM) + @PermissionName(CORE_EDIT_PERM) public SolrJerseyResponse setReplicationEnabled(ReplicationEnabledRequestBody requestBody) { return doSetReplicationEnabled(requestBody); } From d8132d551eb909412b986de89b616625d55a2eac Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Tue, 27 Oct 2026 00:47:10 +0530 Subject: [PATCH 16/18] cl --- changelog/unreleased/SOLR-16398-enable-disable-replication.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog/unreleased/SOLR-16398-enable-disable-replication.yml b/changelog/unreleased/SOLR-16398-enable-disable-replication.yml index f270aded09be..3478a532225c 100644 --- a/changelog/unreleased/SOLR-16398-enable-disable-replication.yml +++ b/changelog/unreleased/SOLR-16398-enable-disable-replication.yml @@ -4,6 +4,8 @@ type: added authors: - name: Jalaz Kumar nick: jaykay12 + - name: Eric Pugh + nick: epugh links: - name: SOLR-16398 url: https://issues.apache.org/jira/browse/SOLR-16398 From 69caade9bb3c9d3012551f27c6771024406500fb Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 26 Oct 2026 20:52:24 -0400 Subject: [PATCH 17/18] Follow existing pattern on these --- .../admin/api/SetReplicationEnabledAPITest.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java index 0e68711cae5d..bcff126ed690 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/SetReplicationEnabledAPITest.java @@ -19,6 +19,7 @@ import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; import org.apache.solr.SolrTestCase; +import org.apache.solr.client.solrj.RemoteSolrException; import org.apache.solr.client.solrj.request.ReplicationApi; import org.apache.solr.common.SolrException; import org.apache.solr.util.ExternalPaths; @@ -57,9 +58,10 @@ public void testMissingEnabledIsRejectedAndStateUnchanged() throws Exception { assertEquals(Boolean.TRUE, fetchReplicationEnabledStatus()); var request = new ReplicationApi.SetReplicationEnabled(CORE_NAME); - SolrException ex = + RemoteSolrException ex = expectThrows( - SolrException.class, () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); + RemoteSolrException.class, + () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); assertEquals(Boolean.TRUE, fetchReplicationEnabledStatus()); @@ -71,9 +73,10 @@ public void testNullEnabledIsRejectedAndStateUnchanged() throws Exception { var request = new ReplicationApi.SetReplicationEnabled(CORE_NAME); request.setEnabled(null); - SolrException ex = + RemoteSolrException ex = expectThrows( - SolrException.class, () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); + RemoteSolrException.class, + () -> request.process(solrTestRule.getSolrClient(CORE_NAME))); assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); assertEquals(Boolean.TRUE, fetchReplicationEnabledStatus()); From 1d7fe6e90847d2a6837c1286e5107f3dca855e00 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 26 Oct 2026 21:45:55 -0400 Subject: [PATCH 18/18] Wire admin UI's enable/disable replication buttons to the new v2 API Add a ReplicationV2 service (mirrors the existing NodeV2 pattern) and switch the leader's Enable/Disable Replication buttons from the v1 ?command=enablereplication/disablereplication calls to PUT /api/cores/{coreName}/replication via the generated SolrJ-equivalent JS client. All other replication actions (polling, replicate-now, abort) remain on v1. --- solr/webapp/web/js/angular/controllers/replication.js | 11 ++++++++++- solr/webapp/web/js/angular/services.js | 6 ++++++ solr/webapp/web/partials/replication.html | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/solr/webapp/web/js/angular/controllers/replication.js b/solr/webapp/web/js/angular/controllers/replication.js index 6e5fd50086a9..42c2e2700f12 100644 --- a/solr/webapp/web/js/angular/controllers/replication.js +++ b/solr/webapp/web/js/angular/controllers/replication.js @@ -16,7 +16,7 @@ */ solrAdminApp.controller('ReplicationController', - function($scope, $rootScope, $routeParams, $interval, $timeout, Replication, Constants) { + function($scope, $rootScope, $routeParams, $interval, $timeout, Replication, ReplicationV2, Constants, ApiErrorHandler) { $scope.resetMenu("replication", Constants.IS_CORE_PAGE); $scope.iterationCount = 1; @@ -58,6 +58,15 @@ solrAdminApp.controller('ReplicationController', Replication.command({core:$routeParams.core, command:command}, function(data){$scope.refresh()}); } + $scope.setReplicationEnabled = function(enabled) { + ReplicationV2.setReplicationEnabled($routeParams.core, {enabled: enabled}, function(error, data, response) { + $timeout(function() { + if (error) { ApiErrorHandler.handle(response); return; } + $scope.refresh(); + }); + }); + } + $scope.showIterations = function() { $scope.iterationCount = 100000}; // limitTo should accept undefined, but doesn't work. $scope.hideIterations = function() { $scope.iterationCount = 1}; diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js index 42105030d765..bde85be4bc34 100644 --- a/solr/webapp/web/js/angular/services.js +++ b/solr/webapp/web/js/angular/services.js @@ -229,6 +229,12 @@ solrAdminServices.factory('Metrics', delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"]; return new solrApi.NodeApi(); }) +.factory('ReplicationV2', + function() { + solrApi.ApiClient.instance.basePath = '/api'; + delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"]; + return new solrApi.ReplicationApi(); + }) .factory('Replication', ['$resource', function($resource) { return $resource(':core/replication', {'wt':'json', core: "@core", '_':Date.now()}, { diff --git a/solr/webapp/web/partials/replication.html b/solr/webapp/web/partials/replication.html index a036b6feee18..629468412694 100644 --- a/solr/webapp/web/partials/replication.html +++ b/solr/webapp/web/partials/replication.html @@ -230,8 +230,8 @@
- - + +