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 000000000000..6b2cc979d50e --- /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 f95335dc407a..8b0d94618df8 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.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 @@ ReplicationDetailsResponse fetchReplicationDetails( @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 000000000000..6eea2d07bd83 --- /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 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 filelist; + + public CommitDetails() {} + + public CommitDetails(long indexVersion, long generation, List 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 9a1054c52402..a4215970f71c 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 @@ 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 static class ReplicationDetails implements ReflectWritable { public Object backup; } - public static class CommitDetails implements ReflectWritable { - @JsonProperty("indexVersion") - public Long indexVersion; - - @JsonProperty("generation") - public Long generation; - - @JsonProperty("filelist") - public List 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 000000000000..8938c1a7ecc2 --- /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 8287459f28f3..87ac3063b6c1 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.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 void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw } 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 void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw 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 @@ private void restore(SolrParams params, SolrQueryResponse rsp, SolrQueryRequest } } - private void populateRestoreStatus(SolrQueryResponse rsp) { - NamedList 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 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 static Long readIntervalNs(String interval) { } } - 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 9985eb05a676..ea0341eb59e3 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.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 ReplicationDetailsResponse fetchReplicationDetails(Boolean follower) { 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 378c5699d31c..a00261b388d5 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.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 @@ protected ReplicationDetailsResponse doFetchReplicationDetails(boolean showFollo 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 478c7c09be6e..0a902ae68356 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.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 @@ private static Object convertReflectWriteableValue(Object value, boolean stringi 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 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 8406f8db35bf..2512e458555e 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.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 void testFetchReplicationDetails() { 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 000000000000..2d9217313dfb --- /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 000000000000..993afee20278 --- /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 b3e8484098e3..781c2c54861e 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.