You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by va...@apache.org on 2015/04/07 21:01:21 UTC

svn commit: r1671919 - in /lucene/dev/branches/branch_5x: ./ solr/ solr/core/ solr/core/src/java/org/apache/solr/handler/ solr/core/src/test/org/apache/solr/handler/

Author: varun
Date: Tue Apr  7 19:01:20 2015
New Revision: 1671919

URL: http://svn.apache.org/r1671919
Log:
SOLR-6637: Solr should have a way to restore a core (merged from trunk r1671022 r1671400)

Added:
    lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/OldBackupDirectory.java
      - copied unchanged from r1671022, lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/OldBackupDirectory.java
    lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/RestoreCore.java
      - copied, changed from r1671022, lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/RestoreCore.java
    lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java
      - copied, changed from r1671022, lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java
Modified:
    lucene/dev/branches/branch_5x/   (props changed)
    lucene/dev/branches/branch_5x/solr/   (props changed)
    lucene/dev/branches/branch_5x/solr/CHANGES.txt   (contents, props changed)
    lucene/dev/branches/branch_5x/solr/core/   (props changed)
    lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
    lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
    lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/SnapShooter.java
    lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/TestReplicationHandlerBackup.java

Modified: lucene/dev/branches/branch_5x/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/CHANGES.txt?rev=1671919&r1=1671918&r2=1671919&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/CHANGES.txt (original)
+++ lucene/dev/branches/branch_5x/solr/CHANGES.txt Tue Apr  7 19:01:20 2015
@@ -32,6 +32,9 @@ Detailed Change List
 New Features
 ----------------------
 
+* SOLR-6637: Solr should have a way to restore a core from a backed up index.
+  (Varun Thacker, noble, shalin)
+
 * SOLR-7241, SOLR-7263, SOLR-7279: More functionality moving the Admin UI to Angular JS
   (Upayavira via Erick)
 

Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java?rev=1671919&r1=1671918&r2=1671919&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java Tue Apr  7 19:01:20 2015
@@ -29,6 +29,7 @@ import java.nio.channels.FileChannel;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
+import java.nio.file.Paths;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -248,31 +249,31 @@ public class IndexFetcher {
     }
   }
 
-  boolean fetchLatestIndex(final SolrCore core, boolean forceReplication) throws IOException, InterruptedException {
-    return fetchLatestIndex(core, forceReplication, false);
+  boolean fetchLatestIndex(boolean forceReplication) throws IOException, InterruptedException {
+    return fetchLatestIndex(forceReplication, false);
   }
   
   /**
    * This command downloads all the necessary files from master to install a index commit point. Only changed files are
    * downloaded. It also downloads the conf files (if they are modified).
    *
-   * @param core the SolrCore
    * @param forceReplication force a replication in all cases 
    * @param forceCoreReload force a core reload in all cases
    * @return true on success, false if slave is already in sync
    * @throws IOException if an exception occurs
    */
-   boolean fetchLatestIndex(final SolrCore core, boolean forceReplication, boolean forceCoreReload) throws IOException, InterruptedException {
+  boolean fetchLatestIndex(boolean forceReplication, boolean forceCoreReload) throws IOException, InterruptedException {
+    
     boolean cleanupDone = false;
     boolean successfulInstall = false;
     replicationStartTime = System.currentTimeMillis();
     Directory tmpIndexDir = null;
-    String tmpIndex = null;
+    String tmpIndex;
     Directory indexDir = null;
-    String indexDirPath = null;
+    String indexDirPath;
     boolean deleteTmpIdxDir = true;
     
-    if (!core.getSolrCoreState().getLastReplicateIndexSuccess()) {
+    if (!solrCore.getSolrCoreState().getLastReplicateIndexSuccess()) {
       // if the last replication was not a success, we force a full replication
       // when we are a bit more confident we may want to try a partial replication
       // if the error is connection related or something, but we have to be careful
@@ -281,7 +282,7 @@ public class IndexFetcher {
     
     try {
       //get the current 'replicateable' index version in the master
-      NamedList response = null;
+      NamedList response;
       try {
         response = getLatestVersion();
       } catch (Exception e) {
@@ -292,12 +293,12 @@ public class IndexFetcher {
       long latestGeneration = (Long) response.get(GENERATION);
 
       // TODO: make sure that getLatestCommit only returns commit points for the main index (i.e. no side-car indexes)
-      IndexCommit commit = core.getDeletionPolicy().getLatestCommit();
+      IndexCommit commit = solrCore.getDeletionPolicy().getLatestCommit();
       if (commit == null) {
         // Presumably the IndexWriter hasn't been opened yet, and hence the deletion policy hasn't been updated with commit points
         RefCounted<SolrIndexSearcher> searcherRefCounted = null;
         try {
-          searcherRefCounted = core.getNewestSearcher(false);
+          searcherRefCounted = solrCore.getNewestSearcher(false);
           if (searcherRefCounted == null) {
             LOG.warn("No open searcher found - fetch aborted");
             return false;
@@ -313,15 +314,14 @@ public class IndexFetcher {
         if (forceReplication && commit.getGeneration() != 0) {
           // since we won't get the files for an empty index,
           // we just clear ours and commit
-          RefCounted<IndexWriter> iw = core.getUpdateHandler().getSolrCoreState().getIndexWriter(core);
+          RefCounted<IndexWriter> iw = solrCore.getUpdateHandler().getSolrCoreState().getIndexWriter(solrCore);
           try {
             iw.get().deleteAll();
           } finally {
             iw.decref();
           }
-          SolrQueryRequest req = new LocalSolrQueryRequest(core,
-              new ModifiableSolrParams());
-          core.getUpdateHandler().commit(new CommitUpdateCommand(req, false));
+          SolrQueryRequest req = new LocalSolrQueryRequest(solrCore, new ModifiableSolrParams());
+          solrCore.getUpdateHandler().commit(new CommitUpdateCommand(req, false));
         }
         
         //there is nothing to be replicated
@@ -341,7 +341,9 @@ public class IndexFetcher {
       // get the list of files first
       fetchFileList(latestGeneration);
       // this can happen if the commit point is deleted before we fetch the file list.
-      if(filesToDownload.isEmpty()) return false;
+      if (filesToDownload.isEmpty()) {
+        return false;
+      }
       LOG.info("Number of files in latest index in master: " + filesToDownload.size());
 
       // Create the sync service
@@ -355,26 +357,18 @@ public class IndexFetcher {
           || commit.getGeneration() >= latestGeneration || forceReplication;
 
       String tmpIdxDirName = "index." + new SimpleDateFormat(SnapShooter.DATE_FMT, Locale.ROOT).format(new Date());
-      tmpIndex = createTempindexDir(core, tmpIdxDirName);
+      tmpIndex = Paths.get(solrCore.getDataDir(), tmpIdxDirName).toString();
 
-      tmpIndexDir = core.getDirectoryFactory().get(tmpIndex, DirContext.DEFAULT, core.getSolrConfig().indexConfig.lockType);
+      tmpIndexDir = solrCore.getDirectoryFactory().get(tmpIndex, DirContext.DEFAULT, solrCore.getSolrConfig().indexConfig.lockType);
       
       // cindex dir...
-      indexDirPath = core.getIndexDir();
-      indexDir = core.getDirectoryFactory().get(indexDirPath, DirContext.DEFAULT, core.getSolrConfig().indexConfig.lockType);
+      indexDirPath = solrCore.getIndexDir();
+      indexDir = solrCore.getDirectoryFactory().get(indexDirPath, DirContext.DEFAULT, solrCore.getSolrConfig().indexConfig.lockType);
 
       try {
 
         SegmentInfos infos = SegmentInfos.readCommit(indexDir, commit.getSegmentsFileName());
-
-        // we treat these files as if they all the oldest version we see
-        Version oldestVersion = Version.LUCENE_CURRENT;
-        for (SegmentCommitInfo commitInfo : infos) {
-          Version version = commitInfo.info.getVersion();
-          if (oldestVersion.onOrAfter(version)) {
-            oldestVersion = version;
-          }
-        }
+        Version oldestVersion = IndexFetcher.checkOldestVersion(infos);
 
         //We will compare all the index files from the master vs the index files on disk to see if there is a mismatch
         //in the metadata. If there is a mismatch for the same index file then we download the entire index again.
@@ -416,7 +410,7 @@ public class IndexFetcher {
           } finally {
             writer.decref();
           }
-          solrCore.getUpdateHandler().getSolrCoreState().closeIndexWriter(core, true);
+          solrCore.getUpdateHandler().getSolrCoreState().closeIndexWriter(solrCore, true);
         }
         boolean reloadCore = false;
         
@@ -434,7 +428,7 @@ public class IndexFetcher {
             reloadCore = true;
             downloadConfFiles(confFilesToDownload, latestGeneration);
             if (isFullCopyNeeded) {
-              successfulInstall = modifyIndexProps(tmpIdxDirName);
+              successfulInstall = IndexFetcher.modifyIndexProps(solrCore, tmpIdxDirName);
               deleteTmpIdxDir = false;
             } else {
               successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
@@ -445,8 +439,8 @@ public class IndexFetcher {
                 // may be closed
                 if (indexDir != null) {
                   LOG.info("removing old index directory " + indexDir);
-                  core.getDirectoryFactory().doneWithDirectory(indexDir);
-                  core.getDirectoryFactory().remove(indexDir);
+                  solrCore.getDirectoryFactory().doneWithDirectory(indexDir);
+                  solrCore.getDirectoryFactory().remove(indexDir);
                 }
               }
               
@@ -458,7 +452,7 @@ public class IndexFetcher {
           } else {
             terminateAndWaitFsyncService();
             if (isFullCopyNeeded) {
-              successfulInstall = modifyIndexProps(tmpIdxDirName);
+              successfulInstall = IndexFetcher.modifyIndexProps(solrCore, tmpIdxDirName);
               deleteTmpIdxDir = false;
             } else {
               successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
@@ -470,13 +464,13 @@ public class IndexFetcher {
           }
         } finally {
           if (!isFullCopyNeeded) {
-            solrCore.getUpdateHandler().getSolrCoreState().openIndexWriter(core);
+            solrCore.getUpdateHandler().getSolrCoreState().openIndexWriter(solrCore);
           }
         }
         
         // we must reload the core after we open the IW back up
        if (successfulInstall && (reloadCore || forceCoreReload)) {
-          LOG.info("Reloading SolrCore {}", core.getName());
+          LOG.info("Reloading SolrCore {}", solrCore.getName());
           reloadCore();
         }
 
@@ -486,8 +480,8 @@ public class IndexFetcher {
             // may be closed
             if (indexDir != null) {
               LOG.info("removing old index directory " + indexDir);
-              core.getDirectoryFactory().doneWithDirectory(indexDir);
-              core.getDirectoryFactory().remove(indexDir);
+              solrCore.getDirectoryFactory().doneWithDirectory(indexDir);
+              solrCore.getDirectoryFactory().remove(indexDir);
             }
           }
           if (isFullCopyNeeded) {
@@ -498,13 +492,13 @@ public class IndexFetcher {
         }
         
         if (!isFullCopyNeeded && !forceReplication && !successfulInstall) {
-          cleanup(core, tmpIndexDir, indexDir, deleteTmpIdxDir, successfulInstall);
+          cleanup(solrCore, tmpIndexDir, indexDir, deleteTmpIdxDir, successfulInstall);
           cleanupDone = true;
           // we try with a full copy of the index
           LOG.warn(
               "Replication attempt was not successful - trying a full index replication reloadCore={}",
               reloadCore);
-          successfulInstall = fetchLatestIndex(core, true, reloadCore);
+          successfulInstall = fetchLatestIndex(true, reloadCore);
         }
         
         replicationStartTime = 0;
@@ -517,15 +511,15 @@ public class IndexFetcher {
       } catch (InterruptedException e) {
         throw new InterruptedException("Index fetch interrupted");
       } catch (Exception e) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Index fetch failed : ", e);
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Index fetch failed : ", e);
       }
     } finally {
       if (!cleanupDone) {
-        cleanup(core, tmpIndexDir, indexDir, deleteTmpIdxDir, successfulInstall);
+        cleanup(solrCore, tmpIndexDir, indexDir, deleteTmpIdxDir, successfulInstall);
       }
     }
   }
-        
+
   private void cleanup(final SolrCore core, Directory tmpIndexDir,
       Directory indexDir, boolean deleteTmpIdxDir, boolean successfulInstall) throws IOException {
     try {
@@ -536,9 +530,9 @@ public class IndexFetcher {
           LOG.error("caught", e);
         }
       }
-      
+
       core.getUpdateHandler().getSolrCoreState().setLastReplicateIndexSuccess(successfulInstall);
-      
+
       filesToDownload = filesDownloaded = confFilesDownloaded = confFilesToDownload = null;
       replicationStartTime = 0;
       dirFileFetcher = null;
@@ -557,11 +551,11 @@ public class IndexFetcher {
           SolrException.log(LOG, "Error removing directory " + tmpIndexDir, e);
         }
       }
-      
+
       if (tmpIndexDir != null) {
         core.getDirectoryFactory().release(tmpIndexDir);
       }
-      
+
       if (indexDir != null) {
         core.getDirectoryFactory().release(indexDir);
       }
@@ -731,15 +725,6 @@ public class IndexFetcher {
     
   }
 
-  /**
-   * All the files are copied to a temp dir first
-   */
-  private String createTempindexDir(SolrCore core, String tmpIdxDirName) {
-    // TODO: there should probably be a DirectoryFactory#concatPath(parent, name)
-    // or something
-    return core.getDataDir() + tmpIdxDirName;
-  }
-
   private void reloadCore() {
     final CountDownLatch latch = new CountDownLatch(1);
     new Thread() {
@@ -828,12 +813,24 @@ public class IndexFetcher {
     || filename.startsWith("segments_") || size < _100K);
   }
 
-  static class CompareResult {
+  protected static Version checkOldestVersion(SegmentInfos infos) {
+    // we treat these files as if they all the oldest version we see
+    Version oldestVersion = Version.LUCENE_CURRENT;
+    for (SegmentCommitInfo commitInfo : infos) {
+      Version version = commitInfo.info.getVersion();
+      if (oldestVersion.onOrAfter(version)) {
+        oldestVersion = version;
+      }
+    }
+    return oldestVersion;
+  }
+
+  protected static class CompareResult {
     boolean equal = false;
     boolean checkSummed = false;
   }
-  
-  private CompareResult compareFile(Directory indexDir, Version version, String filename, Long backupIndexFileLen, Long backupIndexFileChecksum) {
+
+  protected static CompareResult compareFile(Directory indexDir, Version version, String filename, Long backupIndexFileLen, Long backupIndexFileChecksum) {
     CompareResult compareResult = new CompareResult();
     try {
       try (final IndexInput indexInput = indexDir.openInput(filename, IOContext.READONCE)) {
@@ -900,8 +897,8 @@ public class IndexFetcher {
   }  
 
   /**
-   * All the files which are common between master and slave must have same size else we assume they are
-   * not compatible (stale).
+   * All the files which are common between master and slave must have same size and same checksum else we assume
+   * they are not compatible (stale).
    *
    * @return true if the index stale and we need to download a fresh copy, false otherwise.
    * @throws IOException  if low level io error
@@ -1047,7 +1044,7 @@ public class IndexFetcher {
   /**
    * If the index is stale by any chance, load index from a different dir in the data dir.
    */
-  private boolean modifyIndexProps(String tmpIdxDirName) {
+  protected static boolean modifyIndexProps(SolrCore solrCore, String tmpIdxDirName) {
     LOG.info("New index installed. Updating index properties... index="+tmpIdxDirName);
     Properties p = new Properties();
     Directory dir = null;
@@ -1055,7 +1052,7 @@ public class IndexFetcher {
       dir = solrCore.getDirectoryFactory().get(solrCore.getDataDir(), DirContext.META_DATA, solrCore.getSolrConfig().indexConfig.lockType);
       if (slowFileExists(dir, IndexFetcher.INDEX_PROPERTIES)){
         final IndexInput input = dir.openInput(IndexFetcher.INDEX_PROPERTIES, DirectoryFactory.IOCONTEXT_NO_CACHE);
-  
+
         final InputStream is = new PropertiesInputStream(input);
         try {
           p.load(new InputStreamReader(is, StandardCharsets.UTF_8));
@@ -1096,7 +1093,7 @@ public class IndexFetcher {
         }
       }
     }
-    
+
   }
 
   private final Map<String, FileInfo> confFileInfoCache = new HashMap<>();

Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java?rev=1671919&r1=1671918&r2=1671919&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java Tue Apr  7 19:01:20 2015
@@ -36,7 +36,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -147,6 +149,13 @@ public class ReplicationHandler extends
 
   private ReentrantLock indexFetchLock = new ReentrantLock();
 
+  private ExecutorService restoreExecutor = Executors.newSingleThreadExecutor(
+      new DefaultSolrThreadFactory("restoreExecutor"));
+
+  private volatile Future<Boolean> restoreFuture;
+
+  private volatile String currentRestoreName;
+
   private String includeConfFiles;
 
   private NamedList<String> confFileNameAlias = new NamedList<>();
@@ -206,13 +215,13 @@ public class ReplicationHandler extends
     // It gives the current 'replicateable' index version
     if (command.equals(CMD_INDEX_VERSION)) {
       IndexCommit commitPoint = indexCommitPoint;  // make a copy so it won't change
- 
+
       if (commitPoint == null) {
         // if this handler is 'lazy', we may not have tracked the last commit
         // because our commit listener is registered on inform
         commitPoint = core.getDeletionPolicy().getLatestCommit();
       }
-      
+
       if (commitPoint != null && replicationEnabled.get()) {
         //
         // There is a race condition here.  The commit point may be changed / deleted by the time
@@ -236,6 +245,11 @@ public class ReplicationHandler extends
     } else if (command.equalsIgnoreCase(CMD_BACKUP)) {
       doSnapShoot(new ModifiableSolrParams(solrParams), rsp, req);
       rsp.add(STATUS, OK_STATUS);
+    } else if (command.equalsIgnoreCase(CMD_RESTORE)) {
+      restore(new ModifiableSolrParams(solrParams), rsp, req);
+      rsp.add(STATUS, OK_STATUS);
+    } else if (command.equalsIgnoreCase(CMD_RESTORE_STATUS)) {
+      rsp.add(CMD_RESTORE_STATUS, getRestoreStatus());
     } else if (command.equalsIgnoreCase(CMD_DELETE_BACKUP)) {
       deleteSnapshot(new ModifiableSolrParams(solrParams));
       rsp.add(STATUS, OK_STATUS);
@@ -303,7 +317,7 @@ public class ReplicationHandler extends
       throw new SolrException(ErrorCode.BAD_REQUEST, "Missing mandatory param: name");
     }
 
-    SnapShooter snapShooter = new SnapShooter(core, params.get("location"), params.get(NAME));
+    SnapShooter snapShooter = new SnapShooter(core, params.get(LOCATION), params.get(NAME));
     snapShooter.validateDeleteSnapshot();
     snapShooter.deleteSnapAsync(this);
   }
@@ -362,7 +376,7 @@ public class ReplicationHandler extends
       } else {
         currentIndexFetcher = pollingIndexFetcher;
       }
-      return currentIndexFetcher.fetchLatestIndex(core, forceReplication);
+      return currentIndexFetcher.fetchLatestIndex(forceReplication);
     } catch (Exception e) {
       SolrException.log(LOG, "Index fetch failed ", e);
     } finally {
@@ -378,6 +392,72 @@ public class ReplicationHandler extends
     return indexFetchLock.isLocked();
   }
 
+  private void restore(SolrParams params, SolrQueryResponse rsp, SolrQueryRequest req) {
+    if (restoreFuture != null && !restoreFuture.isDone()) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Restore in progress. Cannot run multiple restore operations" +
+          "for the same core");
+    }
+    String name = params.get(NAME);
+    String location = params.get(LOCATION);
+
+    //If location is not provided then assume that the restore index is present inside the data directory.
+    if (location == null) {
+      location = core.getDataDir();
+    }
+
+    //If name is not provided then look for the last unnamed( the ones with the snapshot.timestamp format)
+    //snapshot folder since we allow snapshots to be taken without providing a name. Pick the latest timestamp.
+    if (name == null) {
+      File[] files = new File(location).listFiles();
+      List<OldBackupDirectory> dirs = new ArrayList<>();
+      for (File f : files) {
+        OldBackupDirectory obd = new OldBackupDirectory(f);
+        if (obd.dir != null) {
+          dirs.add(obd);
+        }
+      }
+      Collections.sort(dirs);
+      if (dirs.size() == 0) {
+        throw new SolrException(ErrorCode.BAD_REQUEST, "No backup name specified and none found in " + core.getDataDir());
+      }
+      name = dirs.get(0).dir.getName();
+    } else {
+      //"snapshot." is prefixed by snapshooter
+      name = "snapshot." + name;
+    }
+
+    RestoreCore restoreCore = new RestoreCore(core, location, name);
+    restoreFuture = restoreExecutor.submit(restoreCore);
+    currentRestoreName = name;
+  }
+
+  private NamedList<Object> getRestoreStatus() {
+    NamedList<Object> status = new SimpleOrderedMap<>();
+
+    if (restoreFuture == null) {
+      status.add(STATUS, "No restore actions in progress");
+      return status;
+    }
+
+    status.add("snapshotName", currentRestoreName);
+    if (restoreFuture.isDone()) {
+      try {
+        boolean success = restoreFuture.get();
+        if (success) {
+          status.add(STATUS, SUCCESS);
+        } else {
+          status.add(STATUS, FAILED);
+        }
+      } catch (Exception e) {
+        status.add(STATUS, FAILED);
+        status.add(EXCEPTION, e.getMessage());
+      }
+    } else {
+      status.add(STATUS, "In Progress");
+    }
+    return status;
+  }
+
   private void doSnapShoot(SolrParams params, SolrQueryResponse rsp,
       SolrQueryRequest req) {
     try {
@@ -392,19 +472,19 @@ public class ReplicationHandler extends
       if (numberToKeep < 1) {
         numberToKeep = Integer.MAX_VALUE;
       }
-      
+
       IndexDeletionPolicyWrapper delPolicy = core.getDeletionPolicy();
       IndexCommit indexCommit = delPolicy.getLatestCommit();
-      
+
       if (indexCommit == null) {
         indexCommit = req.getSearcher().getIndexReader().getIndexCommit();
       }
-      
+
       // small race here before the commit point is saved
       SnapShooter snapShooter = new SnapShooter(core, params.get("location"), params.get(NAME));
       snapShooter.validateCreateSnapshot();
       snapShooter.createSnapAsync(indexCommit, numberToKeep, this);
-      
+
     } catch (Exception e) {
       LOG.warn("Exception during creating a snapshot", e);
       rsp.add("exception", e);
@@ -421,7 +501,7 @@ public class ReplicationHandler extends
   private void getFileStream(SolrParams solrParams, SolrQueryResponse rsp) {
     ModifiableSolrParams rawParams = new ModifiableSolrParams(solrParams);
     rawParams.set(CommonParams.WT, FILE_STREAM);
-    
+
     String cfileName = solrParams.get(CONF_FILE_SHORT);
     if (cfileName != null) {
       rsp.add(FILE_STREAM, new LocalFsFileStream(solrParams));
@@ -439,7 +519,7 @@ public class ReplicationHandler extends
     }
     long gen = Long.parseLong(v);
     IndexCommit commit = core.getDeletionPolicy().getCommitPoint(gen);
- 
+
     //System.out.println("ask for files for gen:" + commit.getGeneration() + core.getCoreDescriptor().getCoreContainer().getZkController().getNodeName());
     if (commit == null) {
       rsp.add("status", "invalid index generation");
@@ -463,7 +543,7 @@ public class ReplicationHandler extends
           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)) {
             if (version.onOrAfter(Version.LUCENE_4_8_0)) {
               try {
@@ -474,13 +554,13 @@ public class ReplicationHandler extends
               }
             }
           }
-          
+
           result.add(fileMeta);
         }
       }
       
       // add the segments_N file
-      
+
       // we use the oldest version seen to determine
       // how we treat the segments_N file - conservative, easy
       
@@ -501,7 +581,7 @@ public class ReplicationHandler extends
       result.add(fileMeta);
     } catch (IOException e) {
       rsp.add("status", "unable to get file names for given index generation");
-      rsp.add("exception", e);
+      rsp.add(EXCEPTION, e);
       LOG.error("Unable to get file names for indexCommit generation: " + gen, e);
     } finally {
       if (dir != null) {
@@ -627,7 +707,7 @@ public class ReplicationHandler extends
     return "ReplicationHandler provides replication of index and configuration files from Master to Slaves";
   }
 
-  /** 
+  /**
    * returns the CommitVersionInfo for the current searcher, or null on error.
    */
   private CommitVersionInfo getIndexVersion() {
@@ -708,7 +788,7 @@ public class ReplicationHandler extends
     CommitVersionInfo vInfo = getIndexVersion();
     details.add("indexVersion", null == vInfo ? 0 : vInfo.version);
     details.add(GENERATION, null == vInfo ? 0 : vInfo.generation);
-    
+
     IndexCommit commit = indexCommitPoint;  // make a copy so it won't change
 
     if (isMaster) {
@@ -846,11 +926,11 @@ public class ReplicationHandler extends
       details.add("master", master);
     if (slave.size() > 0)
       details.add("slave", slave);
-    
+
     NamedList snapshotStats = snapShootDetails;
     if (snapshotStats != null)
       details.add(CMD_BACKUP, snapshotStats);
-    
+
     return details;
   }
 
@@ -985,12 +1065,12 @@ public class ReplicationHandler extends
             Boolean.toString(enableMaster) + " and slave setting is " + Boolean.toString(enableSlave));
       }
     }
-    
+
     if (!enableSlave && !enableMaster) {
       enableMaster = true;
       master = new NamedList<>();
     }
-    
+
     if (enableMaster) {
       includeConfFiles = (String) master.get(CONF_FILES);
       if (includeConfFiles != null && includeConfFiles.trim().length() > 0) {
@@ -1013,7 +1093,7 @@ public class ReplicationHandler extends
       if (!replicateOnCommit && ! replicateOnOptimize) {
         replicateOnCommit = true;
       }
-      
+
       // if we only want to replicate on optimize, we need the deletion policy to
       // save the last optimized commit point.
       if (replicateOnOptimize) {
@@ -1082,7 +1162,7 @@ public class ReplicationHandler extends
       isMaster = true;
     }
   }
-  
+
   // check master or slave is enabled
   private boolean isEnabled( NamedList params ){
     if( params == null ) return false;
@@ -1120,6 +1200,19 @@ public class ReplicationHandler extends
       @Override
       public void postClose(SolrCore core) {}
     });
+
+    core.addCloseHook(new CloseHook() {
+      @Override
+      public void preClose(SolrCore core) {
+        ExecutorUtil.shutdownNowAndAwaitTermination(restoreExecutor);
+        if (restoreFuture != null) {
+          restoreFuture.cancel(true);
+        }
+      }
+
+      @Override
+      public void postClose(SolrCore core) {}
+    });
   }
 
   /**
@@ -1421,6 +1514,14 @@ public class ReplicationHandler extends
     return result;
   }
 
+  private static final String LOCATION = "location";
+
+  private static final String SUCCESS = "success";
+
+  private static final String FAILED = "failed";
+
+  private static final String EXCEPTION = "exception";
+
   public static final String MASTER_URL = "masterUrl";
 
   public static final String STATUS = "status";
@@ -1431,6 +1532,10 @@ public class ReplicationHandler extends
 
   public static final String CMD_BACKUP = "backup";
 
+  public static final String CMD_RESTORE = "restore";
+
+  public static final String CMD_RESTORE_STATUS = "restorestatus";
+
   public static final String CMD_FETCH_INDEX = "fetchindex";
 
   public static final String CMD_ABORT_FETCH = "abortfetch";

Copied: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/RestoreCore.java (from r1671022, lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/RestoreCore.java)
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/RestoreCore.java?p2=lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/RestoreCore.java&p1=lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/RestoreCore.java&r1=1671022&r2=1671919&rev=1671919&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/RestoreCore.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/RestoreCore.java Tue Apr  7 19:01:20 2015
@@ -23,10 +23,12 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.Future;
 
 import org.apache.lucene.codecs.CodecUtil;
+import org.apache.lucene.index.SegmentInfos;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.FSDirectory;
 import org.apache.lucene.store.IOContext;
 import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.util.Version;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.SolrCore;
@@ -62,6 +64,8 @@ public class RestoreCore implements Call
     Directory indexDir = null;
     try (Directory backupDir = FSDirectory.open(backupPath)) {
 
+      final Version version = IndexFetcher.checkOldestVersion(SegmentInfos.readLatestCommit(backupDir));
+
       restoreIndexDir = core.getDirectoryFactory().get(restoreIndexPath.toString(),
           DirectoryFactory.DirContext.DEFAULT, core.getSolrConfig().indexConfig.lockType);
 
@@ -72,11 +76,16 @@ public class RestoreCore implements Call
       //Move all files from backupDir to restoreIndexDir
       for (String filename : backupDir.listAll()) {
         checkInterrupted();
-        log.info("Copying over file to restore directory " + filename);
+        log.info("Copying file {} to restore directory ", filename);
         try (IndexInput indexInput = backupDir.openInput(filename, IOContext.READONCE)) {
-          long checksum = CodecUtil.retrieveChecksum(indexInput);
+          Long checksum = null;
+          try {
+            checksum = CodecUtil.retrieveChecksum(indexInput);
+          } catch (Exception e) {
+            log.warn("Could not read checksum from index file: " + filename, e);
+          }
           long length = indexInput.length();
-          IndexFetcher.CompareResult compareResult = IndexFetcher.compareFile(indexDir, filename, length, checksum);
+          IndexFetcher.CompareResult compareResult = IndexFetcher.compareFile(indexDir, version, filename, length, checksum);
           if (!compareResult.equal || (!compareResult.checkSummed && (filename.endsWith(".si")
               || filename.endsWith(".liv") || filename.startsWith("segments_")))) {
             restoreIndexDir.copyFrom(backupDir, filename, filename, IOContext.READONCE);

Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/SnapShooter.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/SnapShooter.java?rev=1671919&r1=1671918&r2=1671919&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/SnapShooter.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/SnapShooter.java Tue Apr  7 19:01:20 2015
@@ -18,6 +18,7 @@ package org.apache.solr.handler;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Paths;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -58,10 +59,11 @@ public class SnapShooter {
 
   public SnapShooter(SolrCore core, String location, String snapshotName) {
     solrCore = core;
-    if (location == null) snapDir = core.getDataDir();
+    if (location == null) {
+      snapDir = core.getDataDir();
+    }
     else  {
-      File base = new File(core.getCoreDescriptor().getInstanceDir());
-      snapDir = org.apache.solr.util.FileUtils.resolvePath(base, location).getAbsolutePath();
+      snapDir = Paths.get(core.getCoreDescriptor().getInstanceDir()).resolve(location).toAbsolutePath().toString();
     }
     this.snapshotName = snapshotName;
 
@@ -125,7 +127,7 @@ public class SnapShooter {
   }
 
   void createSnapshot(final IndexCommit indexCommit, ReplicationHandler replicationHandler) {
-    LOG.info("Creating backup snapshot...");
+    LOG.info("Creating backup snapshot " + (snapshotName == null ? "<not named>" : snapshotName) + " at " + snapDir);
     NamedList<Object> details = new NamedList<>();
     details.add("startTime", new Date().toString());
     try {
@@ -142,7 +144,8 @@ public class SnapShooter {
       details.add("status", "success");
       details.add("snapshotCompletedAt", new Date().toString());
       details.add("snapshotName", snapshotName);
-      LOG.info("Done creating backup snapshot: " + (snapshotName == null ? "<not named>" : snapshotName));
+      LOG.info("Done creating backup snapshot: " + (snapshotName == null ? "<not named>" : snapshotName) +
+          " at " + snapDir);
     } catch (Exception e) {
       IndexFetcher.delTree(snapShotDir);
       LOG.error("Exception while creating snapshot", e);
@@ -193,31 +196,6 @@ public class SnapShooter {
     replicationHandler.snapShootDetails = details;
   }
 
-  private class OldBackupDirectory implements Comparable<OldBackupDirectory>{
-    File dir;
-    Date timestamp;
-    final Pattern dirNamePattern = Pattern.compile("^snapshot[.](.*)$");
-
-    OldBackupDirectory(File dir) {
-      if(dir.isDirectory()) {
-        Matcher m = dirNamePattern.matcher(dir.getName());
-        if(m.find()) {
-          try {
-            this.dir = dir;
-            this.timestamp = new SimpleDateFormat(DATE_FMT, Locale.ROOT).parse(m.group(1));
-          } catch(Exception e) {
-            this.dir = null;
-            this.timestamp = null;
-          }
-        }
-      }
-    }
-    @Override
-    public int compareTo(OldBackupDirectory that) {
-      return that.timestamp.compareTo(this.timestamp);
-    }
-  }
-
   public static final String DATE_FMT = "yyyyMMddHHmmssSSS";
 
 

Modified: lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/TestReplicationHandlerBackup.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/TestReplicationHandlerBackup.java?rev=1671919&r1=1671918&r2=1671919&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/TestReplicationHandlerBackup.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/TestReplicationHandlerBackup.java Tue Apr  7 19:01:20 2015
@@ -121,7 +121,7 @@ public class TestReplicationHandlerBacku
   @Test
   public void testBackupOnCommit() throws Exception {
     //Index
-    int nDocs = indexDocs();
+    int nDocs = indexDocs(masterClient);
 
     //Confirm if completed
     CheckBackupStatus checkBackupStatus = new CheckBackupStatus((HttpSolrClient) masterClient);
@@ -146,7 +146,7 @@ public class TestReplicationHandlerBacku
     }
   }
 
-  private int indexDocs() throws IOException, SolrServerException {
+  protected static int indexDocs(SolrClient masterClient) throws IOException, SolrServerException {
     int nDocs = TestUtil.nextInt(random(), 1, 100);
     masterClient.deleteByQuery("*:*");
     for (int i = 0; i < nDocs; i++) {
@@ -164,7 +164,7 @@ public class TestReplicationHandlerBacku
   @Test
   public void doTestBackup() throws Exception {
 
-    int nDocs = indexDocs();
+    int nDocs = indexDocs(masterClient);
 
     Path[] snapDir = new Path[5]; //One extra for the backup on commit
     //First snapshot location
@@ -180,18 +180,17 @@ public class TestReplicationHandlerBacku
       backupNames = new String[4];
     }
     for (int i = 0; i < 4; i++) {
-      BackupCommand backupCommand;
       final String backupName = TestUtil.randomSimpleString(random(), 1, 20);
       if (!namedBackup) {
-        backupCommand = new BackupCommand(addNumberToKeepInRequest, backupKeepParamName, ReplicationHandler.CMD_BACKUP);
+        if (addNumberToKeepInRequest) {
+          runBackupCommand(masterJetty, ReplicationHandler.CMD_BACKUP, "&" + backupKeepParamName + "=2");
+        } else {
+          runBackupCommand(masterJetty, ReplicationHandler.CMD_BACKUP, "");
+        }
       } else {
-        backupCommand = new BackupCommand(backupName, ReplicationHandler.CMD_BACKUP);
+          runBackupCommand(masterJetty, ReplicationHandler.CMD_BACKUP, "&name=" +  backupName);
         backupNames[i] = backupName;
       }
-      backupCommand.runCommand();
-      if (backupCommand.fail != null) {
-        fail(backupCommand.fail);
-      }
 
       CheckBackupStatus checkBackupStatus = new CheckBackupStatus((HttpSolrClient) masterClient, firstBackupTimestamp);
       while (!checkBackupStatus.success) {
@@ -253,8 +252,7 @@ public class TestReplicationHandlerBacku
   private void testDeleteNamedBackup(String backupNames[]) throws InterruptedException, IOException {
     String lastTimestamp = null;
     for (int i = 0; i < 2; i++) {
-      BackupCommand deleteBackupCommand = new BackupCommand(backupNames[i], ReplicationHandler.CMD_DELETE_BACKUP);
-      deleteBackupCommand.runCommand();
+      runBackupCommand(masterJetty, ReplicationHandler.CMD_DELETE_BACKUP, "&name=" +backupNames[i]);
       CheckDeleteBackupStatus checkDeleteBackupStatus = new CheckDeleteBackupStatus(backupNames[i], lastTimestamp);
       while (true) {
         boolean success = checkDeleteBackupStatus.fetchStatus();
@@ -267,52 +265,19 @@ public class TestReplicationHandlerBacku
         }
         Thread.sleep(200);
       }
-
-      if (deleteBackupCommand.fail != null) {
-        fail(deleteBackupCommand.fail);
-      }
     }
   }
 
-  private class BackupCommand {
-    String fail = null;
-    final boolean addNumberToKeepInRequest;
-    String backupKeepParamName;
-    String backupName;
-    String cmd;
-    
-    BackupCommand(boolean addNumberToKeepInRequest, String backupKeepParamName, String command) {
-      this.addNumberToKeepInRequest = addNumberToKeepInRequest;
-      this.backupKeepParamName = backupKeepParamName;
-      this.cmd = command;
-    }
-    BackupCommand(String backupName, String command) {
-      this.backupName = backupName;
-      addNumberToKeepInRequest = false;
-      this.cmd = command;
-    }
-    
-    public void runCommand() {
-      String masterUrl;
-      if(backupName != null) {
-        masterUrl = buildUrl(masterJetty.getLocalPort(), context) + "/" + DEFAULT_TEST_CORENAME + "/replication?command=" + cmd +
-            "&name=" +  backupName;
-      } else {
-        masterUrl = buildUrl(masterJetty.getLocalPort(), context) + "/" + DEFAULT_TEST_CORENAME + "/replication?command=" + cmd +
-            (addNumberToKeepInRequest ? "&" + backupKeepParamName + "=2" : "");
-      }
-
-      InputStream stream = null;
-      try {
-        URL url = new URL(masterUrl);
-        stream = url.openStream();
-        stream.close();
-      } catch (Exception e) {
-        fail = e.getMessage();
-      } finally {
-        IOUtils.closeQuietly(stream);
-      }
-
+  public static void runBackupCommand(JettySolrRunner masterJetty, String cmd, String params) throws IOException {
+    String masterUrl = buildUrl(masterJetty.getLocalPort(), context) + "/" + DEFAULT_TEST_CORENAME
+        + "/replication?command=" + cmd + params;
+    InputStream stream = null;
+    try {
+      URL url = new URL(masterUrl);
+      stream = url.openStream();
+      stream.close();
+    } finally {
+      IOUtils.closeQuietly(stream);
     }
   }
 
@@ -349,6 +314,6 @@ public class TestReplicationHandlerBacku
         IOUtils.closeQuietly(stream);
       }
       return false;
-    };
+    }
   }
 }

Copied: lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java (from r1671022, lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java)
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java?p2=lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java&p1=lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java&r1=1671022&r2=1671919&rev=1671919&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java Tue Apr  7 19:01:20 2015
@@ -26,6 +26,7 @@ import java.net.URLEncoder;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.commons.io.IOUtils;
@@ -226,8 +227,9 @@ public class TestRestoreCore extends Sol
       URL url = new URL(masterUrl);
       stream = url.openStream();
       String response = IOUtils.toString(stream, "UTF-8");
-      if(pException.matcher(response).find()) {
-        fail("Failed to complete restore action");
+      Matcher matcher = pException.matcher(response);
+      if(matcher.find()) {
+        fail("Failed to complete restore action with exception " + matcher.group(1));
       }
       if(response.contains("<str name=\"status\">success</str>")) {
         return true;