This is an automated email from the ASF dual-hosted git repository. gerlowskija pushed a commit to branch branch_9x in repository https://gitbox.apache.org/repos/asf/solr.git
commit 3847696e38a870f6430e693237f178767695f994 Author: Matthew Biscocho <[email protected]> AuthorDate: Fri Jun 30 13:00:19 2023 -0400 SOLR-16470 : Create V2 equivalent of V1 Replication: GET files (#1704) No v2 equivalent existed prior to this commit. The new v2 API is `GET /api/cores/cName/replication/files?generation=<123>`. --------- Co-authored-by: Jason Gerlowski <[email protected]> --- solr/CHANGES.txt | 3 + .../java/org/apache/solr/handler/IndexFetcher.java | 9 +- .../apache/solr/handler/ReplicationHandler.java | 159 ++++----------------- .../solr/handler/admin/api/CoreReplicationAPI.java | 65 ++++++++- .../solr/handler/admin/api/ReplicationAPIBase.java | 151 ++++++++++++++++++- .../handler/admin/api/CoreReplicationAPITest.java | 37 ++++- .../pages/user-managed-index-replication.adoc | 18 +++ 7 files changed, 299 insertions(+), 143 deletions(-) diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index a540711480e..9c823b016f2 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -168,6 +168,9 @@ Improvements specify where to download Solr from. If the url includes "apache.org", then GPG checks will be computed, otherwise the GPG checks will be skipped. (Houston Putman) +* SOLR-16470: `/coreName/replication?command=filelist` now has a v2 equivalent, available at + `GET /api/cores/coreName/replication/files?generation=123` (Matthew Biscocho via Jason Gerlowski) + Optimizations --------------------- diff --git a/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java b/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java index 34f1395ec97..18a9c60fad7 100644 --- a/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java +++ b/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java @@ -121,6 +121,7 @@ import org.apache.solr.core.DirectoryFactory; import org.apache.solr.core.DirectoryFactory.DirContext; import org.apache.solr.core.IndexDeletionPolicyWrapper; import org.apache.solr.core.SolrCore; +import org.apache.solr.handler.admin.api.CoreReplicationAPI; import org.apache.solr.request.LocalSolrQueryRequest; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.search.SolrIndexSearcher; @@ -1606,14 +1607,14 @@ public class IndexFetcher { names.add(name, null); } // get the details of the local conf files with the same alias/name - List<Map<String, Object>> localFilesInfo = + List<CoreReplicationAPI.FileMetaData> localFilesInfo = replicationHandler.getConfFileInfoFromCache(names, confFileInfoCache); // compare their size/checksum to see if - for (Map<String, Object> fileInfo : localFilesInfo) { - String name = (String) fileInfo.get(NAME); + for (CoreReplicationAPI.FileMetaData fileInfo : localFilesInfo) { + String name = fileInfo.name; Map<String, Object> m = nameVsFile.get(name); if (m == null) continue; // the file is not even present locally (so must be downloaded) - if (m.get(CHECKSUM).equals(fileInfo.get(CHECKSUM))) { + if (m.get(CHECKSUM).equals(fileInfo.checksum)) { nameVsFile.remove(name); // checksums are same so the file need not be downloaded } } 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 3e358fd8fb7..f49ef231f9e 100644 --- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java @@ -56,13 +56,10 @@ import java.util.zip.Adler32; import java.util.zip.Checksum; import java.util.zip.DeflaterOutputStream; import org.apache.commons.io.output.CloseShieldOutputStream; -import org.apache.lucene.codecs.CodecUtil; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexCommit; import org.apache.lucene.index.IndexDeletionPolicy; import org.apache.lucene.index.IndexWriter; -import org.apache.lucene.index.SegmentCommitInfo; -import org.apache.lucene.index.SegmentInfos; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; @@ -272,7 +269,10 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw } else if (command.equals(CMD_GET_FILE)) { getFileStream(solrParams, rsp); } else if (command.equals(CMD_GET_FILE_LIST)) { - getFileList(solrParams, rsp); + final CoreReplicationAPI coreReplicationAPI = new CoreReplicationAPI(core, req, rsp); + V2ApiUtils.squashIntoSolrResponseWithoutHeader( + rsp, + coreReplicationAPI.fetchFileList(Long.parseLong(solrParams.required().get(GENERATION)))); } else if (command.equalsIgnoreCase(CMD_BACKUP)) { doSnapShoot(new ModifiableSolrParams(solrParams), rsp, req); } else if (command.equalsIgnoreCase(CMD_RESTORE)) { @@ -670,113 +670,6 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw rsp.add(STATUS, OK_STATUS); } - private void getFileList(SolrParams solrParams, SolrQueryResponse rsp) { - final IndexDeletionPolicyWrapper delPol = core.getDeletionPolicy(); - final long gen = Long.parseLong(solrParams.required().get(GENERATION)); - - IndexCommit commit = null; - try { - if (gen == -1) { - commit = delPol.getAndSaveLatestCommit(); - if (null == commit) { - rsp.add(CMD_GET_FILE_LIST, Collections.emptyList()); - return; - } - } else { - try { - commit = delPol.getAndSaveCommitPoint(gen); - } catch (IllegalStateException ignored) { - /* handle this below the same way we handle a return value of null... */ - } - if (null == commit) { - // The gen they asked for either doesn't exist or has already been deleted - reportErrorOnResponse(rsp, "invalid index generation", null); - return; - } - } - assert null != commit; - - List<Map<String, Object>> result = new ArrayList<>(); - Directory dir = null; - try { - dir = - core.getDirectoryFactory() - .get( - core.getNewIndexDir(), - DirContext.DEFAULT, - core.getSolrConfig().indexConfig.lockType); - SegmentInfos infos = SegmentInfos.readCommit(dir, commit.getSegmentsFileName()); - for (SegmentCommitInfo commitInfo : infos) { - for (String file : commitInfo.files()) { - Map<String, Object> fileMeta = new HashMap<>(); - fileMeta.put(NAME, file); - fileMeta.put(SIZE, dir.fileLength(file)); - - try (final IndexInput in = dir.openInput(file, IOContext.READONCE)) { - try { - long checksum = CodecUtil.retrieveChecksum(in); - fileMeta.put(CHECKSUM, checksum); - } catch (Exception e) { - // TODO Should this trigger a larger error? - log.warn("Could not read checksum from index file: {}", file, e); - } - } - - result.add(fileMeta); - } - } - - // add the segments_N file - - Map<String, Object> fileMeta = new HashMap<>(); - fileMeta.put(NAME, infos.getSegmentsFileName()); - fileMeta.put(SIZE, dir.fileLength(infos.getSegmentsFileName())); - if (infos.getId() != null) { - try (final IndexInput in = - dir.openInput(infos.getSegmentsFileName(), IOContext.READONCE)) { - try { - fileMeta.put(CHECKSUM, CodecUtil.retrieveChecksum(in)); - } catch (Exception e) { - // TODO Should this trigger a larger error? - log.warn( - "Could not read checksum from index file: {}", infos.getSegmentsFileName(), e); - } - } - } - result.add(fileMeta); - } catch (IOException e) { - log.error( - "Unable to get file names for indexCommit generation: {}", commit.getGeneration(), e); - reportErrorOnResponse(rsp, "unable to get file names for given index generation", e); - return; - } finally { - if (dir != null) { - try { - core.getDirectoryFactory().release(dir); - } catch (IOException e) { - log.error("Could not release directory after fetching file list", e); - } - } - } - rsp.add(CMD_GET_FILE_LIST, result); - - if (confFileNameAlias.size() < 1 || core.getCoreContainer().isZooKeeperAware()) return; - log.debug("Adding config files to list: {}", includeConfFiles); - // if configuration files need to be included get their details - rsp.add(CONF_FILES, getConfFileInfoFromCache(confFileNameAlias, confFileInfoCache)); - rsp.add(STATUS, OK_STATUS); - - } finally { - if (null != commit) { - // before releasing the save on our commit point, set a short reserve duration since - // the main reason remote nodes will ask for the file list is because they are preparing to - // replicate from us... - delPol.setReserveDuration(commit.getGeneration(), reserveCommitDuration); - delPol.releaseCommitPoint(commit); - } - } - } - public CoreReplicationAPI.IndexVersionResponse getIndexVersionResponse() throws IOException { IndexCommit commitPoint = indexCommitPoint; // make a copy so it won't change @@ -828,9 +721,9 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw * <p>The local conf files information is cached so that everytime it does not have to compute the * checksum. The cache is refreshed only if the lastModified of the file changes */ - List<Map<String, Object>> getConfFileInfoFromCache( + public List<CoreReplicationAPI.FileMetaData> getConfFileInfoFromCache( NamedList<String> nameAndAlias, final Map<String, FileInfo> confFileInfoCache) { - List<Map<String, Object>> confFiles = new ArrayList<>(); + List<CoreReplicationAPI.FileMetaData> confFiles = new ArrayList<>(); synchronized (confFileInfoCache) { Checksum checksum = null; for (int i = 0; i < nameAndAlias.size(); i++) { @@ -846,13 +739,13 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw } catch (IOException e) { // proceed with zeroes for now, will probably error on checksum anyway } - if (info == null || info.lastmodified != lastModified || info.size != size) { + if (info == null || info.lastmodified != lastModified || info.fileMetaData.size != size) { if (checksum == null) checksum = new Adler32(); info = new FileInfo(lastModified, cf, size, getCheckSum(checksum, f)); confFileInfoCache.put(cf, info); } - Map<String, Object> m = info.getAsMap(); - if (nameAndAlias.getVal(i) != null) m.put(ALIAS, nameAndAlias.getVal(i)); + CoreReplicationAPI.FileMetaData m = info.fileMetaData; + if (nameAndAlias.getVal(i) != null) m.alias = nameAndAlias.getVal(i); confFiles.add(m); } } @@ -861,23 +754,11 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw static class FileInfo { long lastmodified; - String name; - long size; - long checksum; + CoreReplicationAPI.FileMetaData fileMetaData; public FileInfo(long lasmodified, String name, long size, long checksum) { this.lastmodified = lasmodified; - this.name = name; - this.size = size; - this.checksum = checksum; - } - - Map<String, Object> getAsMap() { - Map<String, Object> map = new HashMap<>(); - map.put(NAME, name); - map.put(SIZE, size); - map.put(CHECKSUM, checksum); - return map; + this.fileMetaData = new CoreReplicationAPI.FileMetaData(size, name, checksum); } } @@ -939,6 +820,22 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw return "ReplicationHandler provides replication of index and configuration files from Leader to Followers"; } + public NamedList<String> getConfFileNameAlias() { + return confFileNameAlias; + } + + public Map<String, FileInfo> getConfFileInfoCache() { + return confFileInfoCache; + } + + public String getIncludeConfFiles() { + return includeConfFiles; + } + + public Long getReserveCommitDuration() { + return reserveCommitDuration; + } + /** returns the CommitVersionInfo for the current searcher, or null on error. */ private CommitVersionInfo getIndexVersion() { try { @@ -1876,7 +1773,7 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw private static final String FAILED = "failed"; - private static final String EXCEPTION = "exception"; + 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/CoreReplicationAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplicationAPI.java index 29ead1bd519..f2c4ac60cd6 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplicationAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplicationAPI.java @@ -20,12 +20,17 @@ import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONT import static org.apache.solr.security.PermissionNameProvider.Name.CORE_READ_PERM; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.Parameter; import java.io.IOException; +import java.util.List; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; import org.apache.solr.core.SolrCore; +import org.apache.solr.jersey.JacksonReflectMapWriter; import org.apache.solr.jersey.PermissionName; import org.apache.solr.jersey.SolrJerseyResponse; import org.apache.solr.request.SolrQueryRequest; @@ -46,12 +51,23 @@ public class CoreReplicationAPI extends ReplicationAPIBase { @GET @Path("/indexversion") - @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2}) @PermissionName(CORE_READ_PERM) public IndexVersionResponse fetchIndexVersion() throws IOException { return doFetchIndexVersion(); } + @GET + @Path("/files") + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2}) + @PermissionName(CORE_READ_PERM) + public FileListResponse fetchFileList( + @Parameter(description = "The generation number of the index", required = true) + @QueryParam("generation") + long gen) { + return doFetchFileList(gen); + } + /** Response for {@link CoreReplicationAPI#fetchIndexVersion()}. */ public static class IndexVersionResponse extends SolrJerseyResponse { @@ -72,4 +88,51 @@ public class CoreReplicationAPI extends ReplicationAPIBase { this.status = status; } } + + /** Response for {@link CoreReplicationAPI#fetchFileList(long)}. */ + public static class FileListResponse extends SolrJerseyResponse { + @JsonProperty("filelist") + public List<FileMetaData> fileList; + + @JsonProperty("confFiles") + public List<FileMetaData> confFiles; + + @JsonProperty("status") + public String status; + + @JsonProperty("message") + public String message; + + @JsonProperty("exception") + public Exception exception; + + public FileListResponse() {} + } + + /** + * Contained in {@link CoreReplicationAPI.FileListResponse}, this holds metadata from a file for + * an index + */ + public static class FileMetaData implements JacksonReflectMapWriter { + + @JsonProperty("size") + public long size; + + @JsonProperty("name") + public String name; + + @JsonProperty("checksum") + public long checksum; + + @JsonProperty("alias") + public String alias; + + public FileMetaData() {} + + public FileMetaData(long size, String name, long checksum) { + this.size = size; + this.name = name; + this.checksum = checksum; + } + } } 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 e53261350ff..f438ecc2ac7 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 @@ -16,16 +16,35 @@ */ package org.apache.solr.handler.admin.api; +import static org.apache.solr.handler.ReplicationHandler.ERR_STATUS; +import static org.apache.solr.handler.ReplicationHandler.OK_STATUS; + import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.index.IndexCommit; +import org.apache.lucene.index.SegmentCommitInfo; +import org.apache.lucene.index.SegmentInfos; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; import org.apache.solr.api.JerseyResource; +import org.apache.solr.core.DirectoryFactory; +import org.apache.solr.core.IndexDeletionPolicyWrapper; import org.apache.solr.core.SolrCore; import org.apache.solr.handler.ReplicationHandler; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** A common parent for "replication" (i.e. replication-level) APIs. */ public abstract class ReplicationAPIBase extends JerseyResource { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); protected final SolrCore solrCore; protected final SolrQueryRequest solrQueryRequest; protected final SolrQueryResponse solrQueryResponse; @@ -38,10 +57,140 @@ public abstract class ReplicationAPIBase extends JerseyResource { } protected CoreReplicationAPI.IndexVersionResponse doFetchIndexVersion() throws IOException { + ReplicationHandler replicationHandler = + (ReplicationHandler) solrCore.getRequestHandler(ReplicationHandler.PATH); + return replicationHandler.getIndexVersionResponse(); + } + protected CoreReplicationAPI.FileListResponse doFetchFileList(long generation) { ReplicationHandler replicationHandler = (ReplicationHandler) solrCore.getRequestHandler(ReplicationHandler.PATH); + return getFileList(generation, replicationHandler); + } - return replicationHandler.getIndexVersionResponse(); + protected CoreReplicationAPI.FileListResponse getFileList( + long generation, ReplicationHandler replicationHandler) { + final IndexDeletionPolicyWrapper delPol = solrCore.getDeletionPolicy(); + final CoreReplicationAPI.FileListResponse filesResponse = + new CoreReplicationAPI.FileListResponse(); + + IndexCommit commit = null; + try { + if (generation == -1) { + commit = delPol.getAndSaveLatestCommit(); + if (null == commit) { + filesResponse.fileList = Collections.emptyList(); + return filesResponse; + } + } else { + try { + commit = delPol.getAndSaveCommitPoint(generation); + } catch (IllegalStateException ignored) { + /* handle this below the same way we handle a return value of null... */ + } + if (null == commit) { + // The gen they asked for either doesn't exist or has already been deleted + reportErrorOnResponse(filesResponse, "invalid index generation", null); + return filesResponse; + } + } + assert null != commit; + + List<CoreReplicationAPI.FileMetaData> result = new ArrayList<>(); + Directory dir = null; + try { + dir = + solrCore + .getDirectoryFactory() + .get( + solrCore.getNewIndexDir(), + DirectoryFactory.DirContext.DEFAULT, + solrCore.getSolrConfig().indexConfig.lockType); + SegmentInfos infos = SegmentInfos.readCommit(dir, commit.getSegmentsFileName()); + for (SegmentCommitInfo commitInfo : infos) { + for (String file : commitInfo.files()) { + CoreReplicationAPI.FileMetaData metaData = new CoreReplicationAPI.FileMetaData(); + metaData.name = file; + metaData.size = dir.fileLength(file); + + try (final IndexInput in = dir.openInput(file, IOContext.READONCE)) { + try { + long checksum = CodecUtil.retrieveChecksum(in); + metaData.checksum = checksum; + } catch (Exception e) { + // TODO Should this trigger a larger error? + log.warn("Could not read checksum from index file: {}", file, e); + } + } + result.add(metaData); + } + } + + // add the segments_N file + CoreReplicationAPI.FileMetaData fileMetaData = new CoreReplicationAPI.FileMetaData(); + fileMetaData.name = infos.getSegmentsFileName(); + fileMetaData.size = dir.fileLength(infos.getSegmentsFileName()); + if (infos.getId() != null) { + try (final IndexInput in = + dir.openInput(infos.getSegmentsFileName(), IOContext.READONCE)) { + try { + fileMetaData.checksum = CodecUtil.retrieveChecksum(in); + } catch (Exception e) { + // TODO Should this trigger a larger error? + log.warn( + "Could not read checksum from index file: {}", infos.getSegmentsFileName(), e); + } + } + } + result.add(fileMetaData); + } catch (IOException e) { + log.error( + "Unable to get file names for indexCommit generation: {}", commit.getGeneration(), e); + reportErrorOnResponse( + filesResponse, "unable to get file names for given index generation", e); + return filesResponse; + } finally { + if (dir != null) { + try { + solrCore.getDirectoryFactory().release(dir); + } catch (IOException e) { + log.error("Could not release directory after fetching file list", e); + } + } + } + filesResponse.fileList = new ArrayList<>(result); + + if (replicationHandler.getConfFileNameAlias().size() < 1 + || solrCore.getCoreContainer().isZooKeeperAware()) return filesResponse; + String includeConfFiles = replicationHandler.getIncludeConfFiles(); + log.debug("Adding config files to list: {}", includeConfFiles); + // if configuration files need to be included get their details + filesResponse.confFiles = + new ArrayList<>( + replicationHandler.getConfFileInfoFromCache( + replicationHandler.getConfFileNameAlias(), + replicationHandler.getConfFileInfoCache())); + filesResponse.status = OK_STATUS; + + } finally { + if (null != commit) { + // before releasing the save on our commit point, set a short reserve duration since + // the main reason remote nodes will ask for the file list is because they are preparing to + // replicate from us... + delPol.setReserveDuration( + commit.getGeneration(), replicationHandler.getReserveCommitDuration()); + delPol.releaseCommitPoint(commit); + } + } + return filesResponse; + } + + private void reportErrorOnResponse( + CoreReplicationAPI.FileListResponse fileListResponse, String message, Exception e) { + fileListResponse.status = ERR_STATUS; + fileListResponse.message = message; + if (e != null) { + fileListResponse.exception = e; + } } } 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 98961280fa1..47faa668ba2 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 @@ -21,6 +21,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import io.opentracing.noop.NoopSpan; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.core.SolrCore; import org.apache.solr.handler.ReplicationHandler; @@ -36,7 +39,6 @@ public class CoreReplicationAPITest extends SolrTestCaseJ4 { private CoreReplicationAPI coreReplicationAPI; private SolrCore mockCore; private ReplicationHandler mockReplicationHandler; - private static final String coreName = "test"; private SolrQueryRequest mockQueryRequest; private SolrQueryResponse queryResponse; @@ -53,7 +55,7 @@ public class CoreReplicationAPITest extends SolrTestCaseJ4 { mockQueryRequest = mock(SolrQueryRequest.class); when(mockQueryRequest.getSpan()).thenReturn(NoopSpan.INSTANCE); queryResponse = new SolrQueryResponse(); - coreReplicationAPI = new CoreReplicationAPI(mockCore, mockQueryRequest, queryResponse); + coreReplicationAPI = new CoreReplicationAPIMock(mockCore, mockQueryRequest, queryResponse); } @Test @@ -62,10 +64,19 @@ public class CoreReplicationAPITest extends SolrTestCaseJ4 { new CoreReplicationAPI.IndexVersionResponse(123L, 123L, "testGeneration"); when(mockReplicationHandler.getIndexVersionResponse()).thenReturn(expected); - CoreReplicationAPI.IndexVersionResponse response = coreReplicationAPI.doFetchIndexVersion(); - assertEquals(expected.indexVersion, response.indexVersion); - assertEquals(expected.generation, response.generation); - assertEquals(expected.status, response.status); + CoreReplicationAPI.IndexVersionResponse actual = coreReplicationAPI.doFetchIndexVersion(); + assertEquals(expected.indexVersion, actual.indexVersion); + assertEquals(expected.generation, actual.generation); + assertEquals(expected.status, actual.status); + } + + @Test + @SuppressWarnings("unchecked") + public void testFetchFiles() throws Exception { + CoreReplicationAPI.FileListResponse actualResponse = coreReplicationAPI.fetchFileList(-1); + assertEquals(123, actualResponse.fileList.get(0).size); + assertEquals("test", actualResponse.fileList.get(0).name); + assertEquals(123456789, actualResponse.fileList.get(0).checksum); } private void setUpMocks() { @@ -73,4 +84,18 @@ public class CoreReplicationAPITest extends SolrTestCaseJ4 { mockReplicationHandler = mock(ReplicationHandler.class); when(mockCore.getRequestHandler(ReplicationHandler.PATH)).thenReturn(mockReplicationHandler); } + + class CoreReplicationAPIMock extends CoreReplicationAPI { + public CoreReplicationAPIMock(SolrCore solrCore, SolrQueryRequest req, SolrQueryResponse rsp) { + super(solrCore, req, rsp); + } + + @Override + protected FileListResponse getFileList(long generation, ReplicationHandler replicationHandler) { + final FileListResponse filesResponse = new FileListResponse(); + List<FileMetaData> fileMetaData = Arrays.asList(new FileMetaData(123, "test", 123456789)); + filesResponse.fileList = new ArrayList<>(fileMetaData); + return filesResponse; + } + } } 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 4def27755a2..0f981c557d7 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 @@ -433,9 +433,27 @@ http://_follower_host:port_/solr/_core_name_/replication?command=details `filelist`:: Retrieve a list of Lucene files present in the specified host's index. + + +==== +[.tab-label]*V1 API* + [source,bash] +---- http://_host:port_/solr/_core_name_/replication?command=filelist&generation=<_generation-number_> + +---- +==== ++ +==== +[.tab-label]*V2 API* + +[source,bash] +---- +http://_host:port_/api/cores/_core_name_/replication/files?generation=<_generation-number_> + +---- +==== + You can discover the generation number of the index by running the `indexversion` command.
