This is an automated email from the ASF dual-hosted git repository.

epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new e59ce3c97dd SOLR-16398: Add V2 JAX-RS APIs for GET replication restore 
status and commits. (#5217)
e59ce3c97dd is described below

commit e59ce3c97ddbe030eafadf0623fdcd80c4529c31
Author: Jalaz Kumar <[email protected]>
AuthorDate: Tue Oct 27 05:14:03 2026 +0530

    SOLR-16398: Add V2 JAX-RS APIs for GET replication restore status and 
commits. (#5217)
    
    Co-authored-by: Eric Pugh <[email protected]>
---
 ...LR-16398-replication-restore-status-commits.yml |  8 +++
 .../solr/client/api/endpoint/ReplicationApis.java  | 18 ++++++
 .../solr/client/api/model/CommitsResponse.java     | 54 ++++++++++++++++
 .../api/model/ReplicationDetailsResponse.java      | 12 +---
 .../client/api/model/RestoreStatusResponse.java    | 67 ++++++++++++++++++++
 .../apache/solr/handler/ReplicationHandler.java    | 72 ++++++++++++----------
 .../solr/handler/admin/api/CoreReplication.java    | 14 +++++
 .../solr/handler/admin/api/ReplicationAPIBase.java | 14 +++++
 .../org/apache/solr/handler/api/V2ApiUtils.java    |  4 ++
 .../handler/admin/api/CoreReplicationAPITest.java  | 36 +++++++++++
 .../handler/admin/api/FetchCommitsAPITest.java     | 57 +++++++++++++++++
 .../admin/api/FetchRestoreStatusAPITest.java       | 58 +++++++++++++++++
 .../pages/user-managed-index-replication.adoc      | 42 +++++++++++++
 13 files changed, 411 insertions(+), 45 deletions(-)

diff --git 
a/changelog/unreleased/SOLR-16398-replication-restore-status-commits.yml 
b/changelog/unreleased/SOLR-16398-replication-restore-status-commits.yml
new file mode 100644
index 00000000000..6b2cc979d50
--- /dev/null
+++ b/changelog/unreleased/SOLR-16398-replication-restore-status-commits.yml
@@ -0,0 +1,8 @@
+title: Add V2 JAX-RS APIs for GET replication restore status and commits.
+type: added
+authors:
+  - name: Jalaz Kumar
+    nick: jaykay12
+links:
+  - name: SOLR-16398
+    url: https://issues.apache.org/jira/browse/SOLR-16398
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 f95335dc407..8b0d94618df 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
@@ -29,9 +29,11 @@ import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.QueryParam;
 import jakarta.ws.rs.core.StreamingOutput;
 import java.io.IOException;
+import org.apache.solr.client.api.model.CommitsResponse;
 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.RestoreStatusResponse;
 import org.apache.solr.client.api.util.CoreApiParameters;
 
 @Path("/cores/{coreName}/replication")
@@ -50,6 +52,22 @@ public interface ReplicationApis {
           @DefaultValue("true")
           Boolean follower);
 
+  @GET
+  @CoreApiParameters
+  @Path("/restore/status")
+  @Operation(
+      summary = "Return the status of the most recent restore operation.",
+      tags = {"replication"})
+  RestoreStatusResponse fetchRestoreStatus();
+
+  @GET
+  @CoreApiParameters
+  @Path("/commits")
+  @Operation(
+      summary = "Return the index commit points retained by the specified 
core.",
+      tags = {"replication"})
+  CommitsResponse fetchCommits();
+
   @GET
   @CoreApiParameters
   @Path("/indexversion")
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/CommitsResponse.java 
b/solr/api/src/java/org/apache/solr/client/api/model/CommitsResponse.java
new file mode 100644
index 00000000000..6eea2d07bd8
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/CommitsResponse.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.List;
+import org.apache.solr.client.api.util.ReflectWritable;
+
+/** Response body for the `GET /api/cores/coreName/replication/commits` API */
+public class CommitsResponse extends SolrJerseyResponse {
+
+  @JsonProperty("commits")
+  @Schema(description = "Index commit points currently retained by the 
deletion policy")
+  public List<CommitDetails> commits;
+
+  @JsonProperty("status")
+  @Schema(description = "Request status, typically OK")
+  public String status;
+
+  /** Details of a single index commit point. */
+  public static class CommitDetails implements ReflectWritable {
+    @JsonProperty("indexVersion")
+    public Long indexVersion;
+
+    @JsonProperty("generation")
+    public Long generation;
+
+    @JsonProperty("filelist")
+    public List<String> filelist;
+
+    public CommitDetails() {}
+
+    public CommitDetails(long indexVersion, long generation, List<String> 
filelist) {
+      this.indexVersion = indexVersion;
+      this.generation = generation;
+      this.filelist = filelist;
+    }
+  }
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/ReplicationDetailsResponse.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationDetailsResponse.java
index 9a1054c5240..a4215970f71 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/model/ReplicationDetailsResponse.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/ReplicationDetailsResponse.java
@@ -19,6 +19,7 @@ package org.apache.solr.client.api.model;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.List;
+import org.apache.solr.client.api.model.CommitsResponse.CommitDetails;
 import org.apache.solr.client.api.util.ReflectWritable;
 
 public class ReplicationDetailsResponse extends SolrJerseyResponse {
@@ -72,17 +73,6 @@ public class ReplicationDetailsResponse extends 
SolrJerseyResponse {
     public Object backup;
   }
 
-  public static class CommitDetails implements ReflectWritable {
-    @JsonProperty("indexVersion")
-    public Long indexVersion;
-
-    @JsonProperty("generation")
-    public Long generation;
-
-    @JsonProperty("filelist")
-    public List<String> filelist;
-  }
-
   public static class LeaderDetails implements ReflectWritable {
     @JsonProperty("confFiles")
     public String confFiles;
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/RestoreStatusResponse.java 
b/solr/api/src/java/org/apache/solr/client/api/model/RestoreStatusResponse.java
new file mode 100644
index 00000000000..8938c1a7ecc
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/RestoreStatusResponse.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+import com.fasterxml.jackson.annotation.JsonValue;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.solr.client.api.util.ReflectWritable;
+
+/** Response body for the `GET /api/cores/coreName/replication/restore` API */
+public class RestoreStatusResponse extends SolrJerseyResponse {
+
+  @JsonProperty("restorestatus")
+  @Schema(description = "State of the most recent restore operation")
+  public RestoreStatus restoreStatus;
+
+  @JsonProperty("status")
+  @Schema(description = "Request status, typically OK")
+  public String status;
+
+  /** State of a restore operation. */
+  public static class RestoreStatus implements ReflectWritable {
+    @JsonProperty("snapshotName")
+    public String snapshotName;
+
+    @JsonProperty("status")
+    @Schema(description = "State of the restore operation")
+    public RestoreState status;
+
+    @JsonProperty("exception")
+    @Schema(description = "Failure message, present only when the restore 
failed with an exception")
+    public String exception;
+  }
+
+  /** State of a restore operation. */
+  public enum RestoreState {
+    NOT_STARTED("No restore actions in progress"),
+    IN_PROGRESS("In Progress"),
+    SUCCESS("success"),
+    FAILED("failed");
+
+    private final String key;
+
+    RestoreState(String key) {
+      this.key = key;
+    }
+
+    @JsonValue
+    public String getKey() {
+      return key;
+    }
+  }
+}
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 8287459f28f..87ac3063b6c 100644
--- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
@@ -75,13 +75,17 @@ import org.apache.lucene.store.IOContext;
 import org.apache.lucene.store.IndexInput;
 import org.apache.solr.api.Api;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.CommitsResponse;
+import org.apache.solr.client.api.model.CommitsResponse.CommitDetails;
 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.ReplicationDetailsResponse.CommitDetails;
 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.RestoreStatusResponse;
+import org.apache.solr.client.api.model.RestoreStatusResponse.RestoreState;
+import org.apache.solr.client.api.model.RestoreStatusResponse.RestoreStatus;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
@@ -311,7 +315,17 @@ public class ReplicationHandler extends RequestHandlerBase
     } else if (command.equalsIgnoreCase(CMD_RESTORE)) {
       restore(new ModifiableSolrParams(solrParams), rsp, req);
     } else if (command.equalsIgnoreCase(CMD_RESTORE_STATUS)) {
-      populateRestoreStatus(rsp);
+      final CoreReplication coreReplicationAPI = new CoreReplication(core, 
req, rsp);
+      final RestoreStatusResponse restoreStatus = 
coreReplicationAPI.fetchRestoreStatus();
+      V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, restoreStatus);
+      if (restoreStatus.restoreStatus.exception != null) {
+        // V1 reports a failed restore as an error response
+        rsp.getValues().remove(STATUS);
+        reportErrorOnResponse(
+            rsp,
+            "Unable to read restorestatus",
+            new Exception(restoreStatus.restoreStatus.exception));
+      }
     } else if (command.equalsIgnoreCase(CMD_DELETE_BACKUP)) {
       deleteSnapshot(new ModifiableSolrParams(solrParams), rsp);
     } else if (command.equalsIgnoreCase(CMD_FETCH_INDEX)) {
@@ -327,7 +341,8 @@ public class ReplicationHandler extends RequestHandlerBase
         reportErrorOnResponse(rsp, "No follower configured", null);
       }
     } else if (command.equals(CMD_SHOW_COMMITS)) {
-      populateCommitInfo(rsp);
+      final CoreReplication coreReplicationAPI = new CoreReplication(core, 
req, rsp);
+      V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, 
coreReplicationAPI.fetchCommits());
     } else if (command.equals(CMD_DETAILS)) {
       final CoreReplication coreReplicationAPI = new CoreReplication(core, 
req, rsp);
       // stringifyBooleans=true: V1's command=details has always rendered 
isLeader/isFollower/etc.
@@ -642,42 +657,35 @@ public class ReplicationHandler extends RequestHandlerBase
     }
   }
 
-  private void populateRestoreStatus(SolrQueryResponse rsp) {
-    NamedList<Object> restoreStatus = new SimpleOrderedMap<>();
-    if (restoreFuture == null) {
-      restoreStatus.add(STATUS, "No restore actions in progress");
-      rsp.add(CMD_RESTORE_STATUS, restoreStatus);
-      rsp.add(STATUS, OK_STATUS);
-      return;
+  public RestoreStatusResponse getRestoreStatusResponse() {
+    RestoreStatusResponse response = new RestoreStatusResponse();
+    response.restoreStatus = new RestoreStatus();
+    response.status = OK_STATUS;
+    Future<Boolean> future = restoreFuture;
+    if (future == null) {
+      response.restoreStatus.status = RestoreState.NOT_STARTED;
+      return response;
     }
 
-    restoreStatus.add("snapshotName", currentRestoreName);
-    if (restoreFuture.isDone()) {
+    response.restoreStatus.snapshotName = currentRestoreName;
+    if (future.isDone()) {
       try {
-        boolean success = restoreFuture.get();
-        if (success) {
-          restoreStatus.add(STATUS, SUCCESS);
-        } else {
-          restoreStatus.add(STATUS, FAILED);
-        }
+        response.restoreStatus.status = future.get() ? RestoreState.SUCCESS : 
RestoreState.FAILED;
       } catch (Exception e) {
-        restoreStatus.add(STATUS, FAILED);
-        restoreStatus.add(EXCEPTION, e.getMessage());
-        rsp.add(CMD_RESTORE_STATUS, restoreStatus);
-        reportErrorOnResponse(rsp, "Unable to read restorestatus", e);
-        return;
+        response.restoreStatus.status = RestoreState.FAILED;
+        response.restoreStatus.exception = e.getMessage() != null ? 
e.getMessage() : e.toString();
       }
     } else {
-      restoreStatus.add(STATUS, "In Progress");
+      response.restoreStatus.status = RestoreState.IN_PROGRESS;
     }
-
-    rsp.add(CMD_RESTORE_STATUS, restoreStatus);
-    rsp.add(STATUS, OK_STATUS);
+    return response;
   }
 
-  private void populateCommitInfo(SolrQueryResponse rsp) {
-    rsp.add(CMD_SHOW_COMMITS, getCommits());
-    rsp.add(STATUS, OK_STATUS);
+  public CommitsResponse getCommitsResponse() {
+    CommitsResponse response = new CommitsResponse();
+    response.commits = getCommitDetails();
+    response.status = OK_STATUS;
+    return response;
   }
 
   private void doSnapShoot(SolrParams params, SolrQueryResponse rsp, 
SolrQueryRequest req) {
@@ -1685,10 +1693,6 @@ public class ReplicationHandler extends 
RequestHandlerBase
     }
   }
 
-  private static final String SUCCESS = "success";
-
-  private static final String FAILED = "failed";
-
   public static final String EXCEPTION = "exception";
 
   public static final String LEADER_URL = "leaderUrl";
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 9985eb05a67..ea0341eb59e 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
@@ -22,9 +22,11 @@ import jakarta.inject.Inject;
 import jakarta.ws.rs.core.StreamingOutput;
 import java.io.IOException;
 import org.apache.solr.client.api.endpoint.ReplicationApis;
+import org.apache.solr.client.api.model.CommitsResponse;
 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.RestoreStatusResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.jersey.PermissionName;
@@ -51,6 +53,18 @@ public class CoreReplication extends ReplicationAPIBase 
implements ReplicationAp
     return doFetchReplicationDetails(follower == null || follower);
   }
 
+  @Override
+  @PermissionName(CORE_READ_PERM)
+  public RestoreStatusResponse fetchRestoreStatus() {
+    return doFetchRestoreStatus();
+  }
+
+  @Override
+  @PermissionName(CORE_READ_PERM)
+  public CommitsResponse fetchCommits() {
+    return doFetchCommits();
+  }
+
   @Override
   @PermissionName(CORE_READ_PERM)
   public IndexVersionResponse fetchIndexVersion() throws IOException {
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 378c5699d31..a00261b388d 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
@@ -45,10 +45,12 @@ import org.apache.lucene.store.IOContext;
 import org.apache.lucene.store.IndexInput;
 import org.apache.lucene.store.RateLimiter;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.CommitsResponse;
 import org.apache.solr.client.api.model.FileListResponse;
 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.RestoreStatusResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.FastOutputStream;
 import org.apache.solr.common.util.IOUtils;
@@ -100,6 +102,18 @@ public abstract class ReplicationAPIBase extends 
JerseyResource {
     return replicationHandler.getReplicationDetails(showFollowerDetails);
   }
 
+  protected RestoreStatusResponse doFetchRestoreStatus() {
+    ReplicationHandler replicationHandler =
+        (ReplicationHandler) 
solrCore.getRequestHandler(ReplicationHandler.PATH);
+    return replicationHandler.getRestoreStatusResponse();
+  }
+
+  protected CommitsResponse doFetchCommits() {
+    ReplicationHandler replicationHandler =
+        (ReplicationHandler) 
solrCore.getRequestHandler(ReplicationHandler.PATH);
+    return replicationHandler.getCommitsResponse();
+  }
+
   protected IndexVersionResponse doFetchIndexVersion() throws IOException {
     ReplicationHandler replicationHandler =
         (ReplicationHandler) 
solrCore.getRequestHandler(ReplicationHandler.PATH);
diff --git a/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java 
b/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java
index 478c7c09be6..0a902ae6835 100644
--- a/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java
+++ b/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java
@@ -34,6 +34,7 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.jersey.SolrJacksonMapper;
 import org.apache.solr.response.PrometheusResponseWriter;
 import org.apache.solr.response.RawResponseWriter;
 import org.apache.solr.response.SolrQueryResponse;
@@ -157,6 +158,9 @@ public class V2ApiUtils {
     if (stringifyBooleans && value instanceof Boolean bool) {
       return bool.toString();
     }
+    if (value instanceof Enum<?> e) {
+      return SolrJacksonMapper.getObjectMapper().convertValue(e, Object.class);
+    }
     if (value instanceof ReflectWritable) {
       NamedList<Object> result = new SimpleOrderedMap<>();
       squashObjectIntoNamedList(result, value, true, stringifyBooleans);
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 8406f8db35b..2512e458555 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
@@ -30,11 +30,14 @@ import java.util.Arrays;
 import java.util.List;
 import org.apache.lucene.util.Constants;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.CommitsResponse;
+import org.apache.solr.client.api.model.CommitsResponse.CommitDetails;
 import org.apache.solr.client.api.model.FileListResponse;
 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.ReplicationDetailsResponse.ReplicationDetails;
+import org.apache.solr.client.api.model.RestoreStatusResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrResourceLoader;
@@ -100,6 +103,39 @@ public class CoreReplicationAPITest extends SolrTestCaseJ4 
{
     assertSame(expectedResponse, withFollowerFalse);
   }
 
+  @Test
+  public void testGetRestoreStatus() {
+    RestoreStatusResponse expected = new RestoreStatusResponse();
+    expected.status = "OK";
+    expected.restoreStatus = new RestoreStatusResponse.RestoreStatus();
+    expected.restoreStatus.snapshotName = "snapshot.test";
+    expected.restoreStatus.status = RestoreStatusResponse.RestoreState.FAILED;
+    expected.restoreStatus.exception = "restore failed";
+    
when(mockReplicationHandler.getRestoreStatusResponse()).thenReturn(expected);
+
+    RestoreStatusResponse actual = coreReplicationAPI.doFetchRestoreStatus();
+    assertEquals(expected.status, actual.status);
+    assertEquals(expected.restoreStatus.snapshotName, 
actual.restoreStatus.snapshotName);
+    assertEquals(expected.restoreStatus.status, actual.restoreStatus.status);
+    assertEquals(expected.restoreStatus.exception, 
actual.restoreStatus.exception);
+  }
+
+  @Test
+  public void testGetCommits() {
+    CommitDetails commit = new CommitDetails(42L, 7L, List.of("segments_7", 
"_0.cfs"));
+    CommitsResponse expected = new CommitsResponse();
+    expected.status = "OK";
+    expected.commits = List.of(commit);
+    when(mockReplicationHandler.getCommitsResponse()).thenReturn(expected);
+
+    CommitsResponse actual = coreReplicationAPI.doFetchCommits();
+    assertEquals(expected.status, actual.status);
+    assertEquals(1, actual.commits.size());
+    assertEquals(expected.commits.getFirst().indexVersion, 
actual.commits.getFirst().indexVersion);
+    assertEquals(expected.commits.getFirst().generation, 
actual.commits.getFirst().generation);
+    assertEquals(expected.commits.getFirst().filelist, 
actual.commits.getFirst().filelist);
+  }
+
   @Test
   public void testGetIndexVersion() throws Exception {
     IndexVersionResponse expected = new IndexVersionResponse(123L, 123L, 
"testGeneration");
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/FetchCommitsAPITest.java 
b/solr/core/src/test/org/apache/solr/handler/admin/api/FetchCommitsAPITest.java
new file mode 100644
index 00000000000..2d9217313df
--- /dev/null
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/FetchCommitsAPITest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.util.ExternalPaths;
+import org.apache.solr.util.SolrJettyTestRule;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * HTTP tests for {@code GET /api/cores/coreName/replication/commits} via the 
generated SolrJ
+ * request.
+ */
+public class FetchCommitsAPITest extends SolrTestCase {
+
+  private static final String CORE_NAME = "commitsApiTestCore";
+
+  @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 testFetchCommits() throws Exception {
+    var request = new ReplicationApi.FetchCommits(CORE_NAME);
+    var response = request.process(solrTestRule.getSolrClient(CORE_NAME));
+
+    assertNotNull(response);
+    assertNull(response.error);
+    assertEquals("OK", response.status);
+    assertNotNull(response.commits);
+    assertFalse(response.commits.isEmpty());
+  }
+}
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/FetchRestoreStatusAPITest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/FetchRestoreStatusAPITest.java
new file mode 100644
index 00000000000..993afee2027
--- /dev/null
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/FetchRestoreStatusAPITest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.api.model.RestoreStatusResponse;
+import org.apache.solr.client.solrj.request.ReplicationApi;
+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 GET /api/cores/coreName/replication/restore/status} 
via the generated SolrJ
+ * request.
+ */
+public class FetchRestoreStatusAPITest extends SolrTestCase {
+
+  private static final String CORE_NAME = "restoreStatusApiTestCore";
+
+  @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 testFetchRestoreStatus() throws Exception {
+    var request = new ReplicationApi.FetchRestoreStatus(CORE_NAME);
+    var response = request.process(solrTestRule.getSolrClient(CORE_NAME));
+
+    assertNotNull(response);
+    assertNull(response.error);
+    assertEquals("OK", response.status);
+    assertNotNull(response.restoreStatus);
+    assertEquals(RestoreStatusResponse.RestoreState.NOT_STARTED, 
response.restoreStatus.status);
+  }
+}
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 b3e8484098e..781c2c54861 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
@@ -427,6 +427,30 @@ Disable the specified follower from polling for changes on 
the leader.
 [source,bash]
 http://{followerHost}:{port}/solr/{coreName}/replication?command=disablepoll
 
+`commits`::
+Retrieve the index commit points currently retained by the core, with each 
commit's index version, generation, and the file list.
++
+[tabs#commits-request]
+======
+V1 API::
++
+====
+[source,bash]
+----
+http://{leaderHost}:{port}/solr/{coreName}/replication?command=commits
+----
+====
+
+V2 API::
++
+====
+[source,bash]
+----
+http://{leaderHost}:{port}/api/cores/{coreName}/replication/commits
+----
+====
+======
+
 `details`::
 Retrieve configuration details and current status.
 +
@@ -571,8 +595,26 @@ For file system repository, location defaults to core's 
dataDir (data directory)
 `restorestatus`::
 Check the status of a running restore operation.
 +
+[tabs#restorestatus-request]
+======
+V1 API::
++
+====
 [source,bash]
+----
 http://{leaderHost}:{port}/solr/{coreName}/replication?command=restorestatus
+----
+====
+
+V2 API::
++
+====
+[source,bash]
+----
+http://{leaderHost}:{port}/api/cores/{coreName}/replication/restore/status
+----
+====
+======
 +
 This command is used to check the status of a restore operation.
 This command takes no parameters.

Reply via email to