This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_10x in repository https://gitbox.apache.org/repos/asf/solr.git
commit a6018e09a3351bfb25ffc5fd70304ba7620e089a Author: Jason Gerlowski <[email protected]> AuthorDate: Mon Oct 12 06:38:52 2026 -0400 SOLR-18502: INSTALLSHARD skips write.lock download (#5026) (cherry picked from commit f3a798e424bb933ba988c8d316d0e8f35c8b6e0f) --- ...LR-18502-ignore-write-locks-in-installshard.yml | 8 ++++ .../java/org/apache/solr/handler/RestoreCore.java | 6 +++ .../api/collections/AbstractInstallShardTest.java | 46 +++++++++++++++++++++- .../apache/solr/core/TrackingBackupRepository.java | 13 ++++++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/changelog/unreleased/SOLR-18502-ignore-write-locks-in-installshard.yml b/changelog/unreleased/SOLR-18502-ignore-write-locks-in-installshard.yml new file mode 100644 index 00000000000..fa929f97131 --- /dev/null +++ b/changelog/unreleased/SOLR-18502-ignore-write-locks-in-installshard.yml @@ -0,0 +1,8 @@ +title: > + INSTALLSHARD now ignores 'write.lock' file if present in offline index data. +type: changed +authors: + - name: Jason Gerlowski +links: + - name: SOLR-18502 + url: https://issues.apache.org/jira/browse/SOLR-18502 diff --git a/solr/core/src/java/org/apache/solr/handler/RestoreCore.java b/solr/core/src/java/org/apache/solr/handler/RestoreCore.java index 2d5e1f2fa92..fecfc6d58de 100644 --- a/solr/core/src/java/org/apache/solr/handler/RestoreCore.java +++ b/solr/core/src/java/org/apache/solr/handler/RestoreCore.java @@ -34,6 +34,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.index.IndexWriter; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexInput; @@ -145,6 +146,11 @@ public class RestoreCore implements Callable<Boolean> { for (String filename : repository.listAllFiles()) { checkInterrupted(); + // Lock files only make sense for the process that created them, and carry no checksum + if (IndexWriter.WRITE_LOCK_NAME.equals(filename)) { + continue; + } + // Capture variables for lambda final String filenameFinal = filename; final boolean fileExistsLocally = indexDirFiles.contains(filename); diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractInstallShardTest.java b/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractInstallShardTest.java index 86d12f4a7bd..17e20cbeed8 100644 --- a/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractInstallShardTest.java +++ b/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractInstallShardTest.java @@ -35,6 +35,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.apache.lucene.index.IndexWriter; import org.apache.lucene.store.Directory; import org.apache.solr.client.solrj.RemoteSolrException; import org.apache.solr.client.solrj.SolrClient; @@ -50,6 +51,7 @@ import org.apache.solr.core.CoreContainer; import org.apache.solr.core.CoreDescriptor; import org.apache.solr.core.DirectoryFactory; import org.apache.solr.core.SolrCore; +import org.apache.solr.core.TrackingBackupRepository; import org.apache.solr.core.backup.repository.BackupRepository; import org.apache.solr.handler.admin.api.InstallShardData; import org.junit.After; @@ -187,6 +189,41 @@ public abstract class AbstractInstallShardTest extends SolrCloudTestCase { assertCollectionHasNumDocs(collectionName, singleShardNumDocs); } + @Test + public void testInstallSkipsWriteLockFile() throws Exception { + final String collectionName = createAndAwaitEmptyCollection(1, replicasPerShard); + deleteAfterTest(collectionName); + enableReadOnly(collectionName); + + // Offline index data _shouldn't_ contain a "write.lock", but it's common for users + // to copy a live index dir, so the file is often included regardless. Make sure + // write.lock is present to simulate this case so that we can ensure INSTALLSHARD + // skips it gracefully. + final CoreContainer cc = cluster.getJettySolrRunner(0).getCoreContainer(); + final URI lockUri; + try (final BackupRepository repo = cc.newBackupRepository(BACKUP_REPO_NAME)) { + lockUri = repo.resolve(singleShard1Uri, "write.lock"); + assertTrue("Test data should include a write.lock", repo.exists(lockUri)); + } + + TrackingBackupRepository.clear(); + CollectionAdminRequest.installDataToShard( + collectionName, "shard1", singleShard1Uri.toString(), BACKUP_REPO_NAME) + .process(cluster.getSolrClient()); + waitForState( + "The core-installs should complete and all replicas become healthy", + collectionName, + 30, + TimeUnit.SECONDS, + SolrCloudTestCase.activeClusterShape(1, replicasPerShard)); + + assertCollectionHasNumDocs(collectionName, singleShardNumDocs); + + final List<URI> restored = TrackingBackupRepository.restoredFiles(); + assertFalse("Expected other index files to be installed", restored.isEmpty()); + assertFalse("write.lock should not be copied during install", restored.contains(lockUri)); + } + @Test public void testInstallReportsErrorsAppropriately() throws Exception { final String collectionName = createAndAwaitEmptyCollection(1, replicasPerShard); @@ -355,7 +392,14 @@ public abstract class AbstractInstallShardTest extends SolrCloudTestCase { core.getSolrConfig().indexConfig.lockType); try { for (final String dirContent : dir.listAll()) { - if (dirContent.contains("write.lock")) continue; + if (dirContent.equals(IndexWriter.WRITE_LOCK_NAME)) { + // Backup upload requires a checksum footer that lock files lack, so create it directly. + // Real-world "offline" indices copied from a live index dir often contain one. + backupRepository + .createOutput(backupRepository.resolve(destinationUri, dirContent)) + .close(); + continue; + } backupRepository.copyFileFrom(dir, dirContent, destinationUri); } } finally { diff --git a/solr/test-framework/src/java/org/apache/solr/core/TrackingBackupRepository.java b/solr/test-framework/src/java/org/apache/solr/core/TrackingBackupRepository.java index f81d59d1d4a..4148bc667ab 100644 --- a/solr/test-framework/src/java/org/apache/solr/core/TrackingBackupRepository.java +++ b/solr/test-framework/src/java/org/apache/solr/core/TrackingBackupRepository.java @@ -28,6 +28,7 @@ import org.apache.solr.core.backup.repository.DelegatingBackupRepository; public class TrackingBackupRepository extends DelegatingBackupRepository { private static final List<URI> COPIED_FILES = Collections.synchronizedList(new ArrayList<>()); + private static final List<URI> RESTORED_FILES = Collections.synchronizedList(new ArrayList<>()); private static final List<URI> DIRECTORIES_CREATED = Collections.synchronizedList(new ArrayList<>()); private static final List<URI> OUTPUTS_CREATED = Collections.synchronizedList(new ArrayList<>()); @@ -52,6 +53,12 @@ public class TrackingBackupRepository extends DelegatingBackupRepository { super.copyIndexFileFrom(sourceDir, sourceFileName, destDir, destFileName); } + @Override + public void copyFileTo(URI sourceRepo, String fileName, Directory dest) throws IOException { + RESTORED_FILES.add(resolve(sourceRepo, fileName)); + super.copyFileTo(sourceRepo, fileName, dest); + } + /** * @return list of files were copied by using {@link #copyFileFrom(Directory, String, URI)} */ @@ -59,6 +66,11 @@ public class TrackingBackupRepository extends DelegatingBackupRepository { return new ArrayList<>(COPIED_FILES); } + /** Repository files that were copied into a Directory, i.e. restored/installed */ + public static List<URI> restoredFiles() { + return new ArrayList<>(RESTORED_FILES); + } + public static List<URI> directoriesCreated() { return new ArrayList<>(DIRECTORIES_CREATED); } @@ -70,6 +82,7 @@ public class TrackingBackupRepository extends DelegatingBackupRepository { /** Clear all tracking data */ public static void clear() { COPIED_FILES.clear(); + RESTORED_FILES.clear(); DIRECTORIES_CREATED.clear(); OUTPUTS_CREATED.clear(); }
