SOLR-5340: Add support for named snapshots

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1592957 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Noble Paul 2014-05-07 10:12:29 +00:00
parent af86a78df5
commit 4a84567693
4 changed files with 228 additions and 54 deletions

View File

@ -168,6 +168,8 @@ Other Changes
* SOLR_3671: Fix DIHWriter interface usage so users may implement writers that output
documents to a location external to Solr (ex. a NoSql db). (Roman Chyla via James Dyer)
* SOLR-5340: Add support for named snapshots (Varun Thacker via Noble Paul)
Build
---------------------

View File

@ -208,7 +208,10 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
} else if (command.equals(CMD_GET_FILE_LIST)) {
getFileList(solrParams, rsp);
} else if (command.equalsIgnoreCase(CMD_BACKUP)) {
doSnapShoot(new ModifiableSolrParams(solrParams), rsp,req);
doSnapShoot(new ModifiableSolrParams(solrParams), rsp, req);
rsp.add(STATUS, OK_STATUS);
} else if (command.equalsIgnoreCase(CMD_DELETE_BACKUP)) {
deleteSnapshot(new ModifiableSolrParams(solrParams), rsp, req);
rsp.add(STATUS, OK_STATUS);
} else if (command.equalsIgnoreCase(CMD_FETCH_INDEX)) {
String masterUrl = solrParams.get(MASTER_URL);
@ -257,7 +260,7 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
} else if (command.equals(CMD_SHOW_COMMITS)) {
rsp.add(CMD_SHOW_COMMITS, getCommits());
} else if (command.equals(CMD_DETAILS)) {
rsp.add(CMD_DETAILS, getReplicationDetails(solrParams.getBool("slave",true)));
rsp.add(CMD_DETAILS, getReplicationDetails(solrParams.getBool("slave", true)));
RequestHandlerUtils.addExperimentalFormatWarning(rsp);
} else if (CMD_ENABLE_REPL.equalsIgnoreCase(command)) {
replicationEnabled.set(true);
@ -268,6 +271,17 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
}
}
private void deleteSnapshot(ModifiableSolrParams params, SolrQueryResponse rsp, SolrQueryRequest req) {
String name = params.get("name");
if(name == null) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Missing mandatory param: name");
}
SnapShooter snapShooter = new SnapShooter(core, params.get("location"), params.get("name"));
snapShooter.validateDeleteSnapshot();
snapShooter.deleteSnapAsync(this);
}
private List<NamedList<Object>> getCommits() {
Map<Long, IndexCommit> commits = core.getDeletionPolicy().getCommits();
List<NamedList<Object>> l = new ArrayList<>();
@ -359,8 +373,9 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
}
// small race here before the commit point is saved
new SnapShooter(core, params.get("location")).createSnapAsync(
indexCommit, numberToKeep, this);
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);
@ -1067,7 +1082,7 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
if (numberToKeep < 1) {
numberToKeep = Integer.MAX_VALUE;
}
SnapShooter snapShooter = new SnapShooter(core, null);
SnapShooter snapShooter = new SnapShooter(core, null, null);
snapShooter.createSnapAsync(currentCommitPoint, numberToKeep, ReplicationHandler.this);
} catch (Exception e) {
LOG.error("Exception while snapshooting", e);
@ -1305,6 +1320,8 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
public static final String CMD_SHOW_COMMITS = "commits";
public static final String CMD_DELETE_BACKUP = "deletebackup";
public static final String GENERATION = "generation";
public static final String OFFSET = "offset";

View File

@ -33,6 +33,7 @@ import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.Lock;
import org.apache.lucene.store.SimpleFSLockFactory;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.DirectoryFactory;
import org.apache.solr.core.DirectoryFactory.DirContext;
@ -52,8 +53,12 @@ public class SnapShooter {
private String snapDir = null;
private SolrCore solrCore;
private SimpleFSLockFactory lockFactory;
public SnapShooter(SolrCore core, String location) {
private String snapshotName = null;
private String directoryName = null;
private File snapShotDir = null;
private Lock lock = null;
public SnapShooter(SolrCore core, String location, String snapshotName) {
solrCore = core;
if (location == null) snapDir = core.getDataDir();
else {
@ -63,10 +68,14 @@ public class SnapShooter {
if (!dir.exists()) dir.mkdirs();
}
lockFactory = new SimpleFSLockFactory(snapDir);
}
void createSnapAsync(final IndexCommit indexCommit, final ReplicationHandler replicationHandler) {
createSnapAsync(indexCommit, Integer.MAX_VALUE, replicationHandler);
this.snapshotName = snapshotName;
if(snapshotName != null) {
directoryName = "snapshot." + snapshotName;
} else {
SimpleDateFormat fmt = new SimpleDateFormat(DATE_FMT, Locale.ROOT);
directoryName = "snapshot." + fmt.format(new Date());
}
}
void createSnapAsync(final IndexCommit indexCommit, final int numberToKeep, final ReplicationHandler replicationHandler) {
@ -75,34 +84,67 @@ public class SnapShooter {
new Thread() {
@Override
public void run() {
createSnapshot(indexCommit, numberToKeep, replicationHandler);
if(snapshotName != null) {
createSnapshot(indexCommit, replicationHandler);
} else {
deleteOldBackups(numberToKeep);
createSnapshot(indexCommit, replicationHandler);
}
}
}.start();
}
void createSnapshot(final IndexCommit indexCommit, int numberToKeep, ReplicationHandler replicationHandler) {
public void validateDeleteSnapshot() {
boolean dirFound = false;
File[] files = new File(snapDir).listFiles();
for(File f : files) {
if (f.getName().equals("snapshot." + snapshotName)) {
dirFound = true;
break;
}
}
if(dirFound == false) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Snapshot cannot be found in directory: " + snapDir);
}
}
protected void deleteSnapAsync(final ReplicationHandler replicationHandler) {
new Thread() {
@Override
public void run() {
deleteNamedSnapshot(replicationHandler);
}
}.start();
}
void validateCreateSnapshot() throws IOException {
Lock lock = lockFactory.makeLock(directoryName + ".lock");
if (lock.isLocked()) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Unable to acquire lock for snapshot directory: " + snapShotDir.getAbsolutePath());
}
snapShotDir = new File(snapDir, directoryName);
if (snapShotDir.exists()) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Snapshot directory already exists: " + snapShotDir.getAbsolutePath());
}
if (!snapShotDir.mkdirs()) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Unable to create snapshot directory: " + snapShotDir.getAbsolutePath());
}
}
void createSnapshot(final IndexCommit indexCommit, ReplicationHandler replicationHandler) {
LOG.info("Creating backup snapshot...");
NamedList<Object> details = new NamedList<>();
details.add("startTime", new Date().toString());
File snapShotDir = null;
String directoryName = null;
Lock lock = null;
try {
if(numberToKeep<Integer.MAX_VALUE) {
deleteOldBackups(numberToKeep);
}
SimpleDateFormat fmt = new SimpleDateFormat(DATE_FMT, Locale.ROOT);
directoryName = "snapshot." + fmt.format(new Date());
lock = lockFactory.makeLock(directoryName + ".lock");
if (lock.isLocked()) return;
snapShotDir = new File(snapDir, directoryName);
if (!snapShotDir.mkdir()) {
LOG.warn("Unable to create snapshot directory: " + snapShotDir.getAbsolutePath());
return;
}
Collection<String> files = indexCommit.getFileNames();
FileCopier fileCopier = new FileCopier();
Directory dir = solrCore.getDirectoryFactory().get(solrCore.getIndexDir(), DirContext.DEFAULT, solrCore.getSolrConfig().indexConfig.lockType);
try {
fileCopier.copyFiles(dir, files, snapShotDir);
@ -129,6 +171,7 @@ public class SnapShooter {
}
}
}
private void deleteOldBackups(int numberToKeep) {
File[] files = new File(snapDir).listFiles();
List<OldBackupDirectory> dirs = new ArrayList<>();
@ -138,6 +181,10 @@ public class SnapShooter {
dirs.add(obd);
}
}
if(numberToKeep > dirs.size()) {
return;
}
Collections.sort(dirs);
int i=1;
for(OldBackupDirectory dir : dirs) {
@ -146,6 +193,24 @@ public class SnapShooter {
}
}
}
protected void deleteNamedSnapshot(ReplicationHandler replicationHandler) {
LOG.info("Deleting snapshot: " + snapshotName);
NamedList<Object> details = new NamedList<>();
boolean isSuccess = false;
File f = new File(snapDir, "snapshot." + snapshotName);
isSuccess = SnapPuller.delTree(f);
if(isSuccess) {
details.add("status", "success");
} else {
details.add("status", "Unable to delete snapshot: " + snapshotName);
LOG.warn("Unable to delete snapshot: " + snapshotName);
}
replicationHandler.snapShootDetails = details;
}
private class OldBackupDirectory implements Comparable<OldBackupDirectory>{
File dir;
Date timestamp;

View File

@ -1319,7 +1319,7 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
addNumberToKeepInRequest = false;
backupKeepParamName = ReplicationHandler.NUMBER_BACKUPS_TO_KEEP_INIT_PARAM;
}
masterJetty.stop();
master.copyConfigFile(CONF_DIR + configFile,
"solrconfig.xml");
@ -1339,15 +1339,29 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
volatile String fail = null;
final boolean addNumberToKeepInRequest;
String backupKeepParamName;
BackupThread(boolean addNumberToKeepInRequest, String backupKeepParamName) {
String backupName;
String cmd;
BackupThread(boolean addNumberToKeepInRequest, String backupKeepParamName, String command) {
this.addNumberToKeepInRequest = addNumberToKeepInRequest;
this.backupKeepParamName = backupKeepParamName;
this.cmd = command;
}
BackupThread(String backupName, String command) {
this.backupName = backupName;
addNumberToKeepInRequest = false;
this.cmd = command;
}
@Override
public void run() {
String masterUrl =
buildUrl(masterJetty.getLocalPort()) + "/replication?command=" + ReplicationHandler.CMD_BACKUP +
(addNumberToKeepInRequest ? "&" + backupKeepParamName + "=1" : "");
String masterUrl = null;
if(backupName != null) {
masterUrl = buildUrl(masterJetty.getLocalPort()) + "/replication?command=" + cmd +
"&name=" + backupName;
} else {
masterUrl = buildUrl(masterJetty.getLocalPort()) + "/replication?command=" + cmd +
(addNumberToKeepInRequest ? "&" + backupKeepParamName + "=1" : "");
}
URL url;
InputStream stream = null;
try {
@ -1362,20 +1376,45 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
};
};
class CheckDeleteBackupStatus {
String response = null;
boolean success = false;
String fail = null;
public void fetchStatus() {
String masterUrl = buildUrl(masterJetty.getLocalPort()) + "/replication?command=" + ReplicationHandler.CMD_DETAILS;
URL url;
InputStream stream = null;
try {
url = new URL(masterUrl);
stream = url.openStream();
response = IOUtils.toString(stream, "UTF-8");
if(response.contains("<str name=\"status\">success</str>")) {
success = true;
}
stream.close();
} catch (Exception e) {
fail = e.getMessage();
} finally {
IOUtils.closeQuietly(stream);
}
};
}
class CheckStatus extends Thread {
volatile String fail = null;
volatile String response = null;
volatile boolean success = false;
volatile String backupTimestamp = null;
class CheckBackupStatus {
String fail = null;
String response = null;
boolean success = false;
String backupTimestamp = null;
final String lastBackupTimestamp;
final Pattern p = Pattern.compile("<str name=\"snapshotCompletedAt\">(.*?)</str>");
CheckStatus(String lastBackupTimestamp) {
CheckBackupStatus(String lastBackupTimestamp) {
this.lastBackupTimestamp = lastBackupTimestamp;
}
@Override
public void run() {
public void fetchStatus() {
String masterUrl = buildUrl(masterJetty.getLocalPort()) + "/replication?command=" + ReplicationHandler.CMD_DETAILS;
URL url;
InputStream stream = null;
@ -1405,29 +1444,41 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
File[] snapDir = new File[2];
String firstBackupTimestamp = null;
boolean namedBackup = true;
String[] backupNames = null;
if(namedBackup) {
backupNames = new String[2];
}
for(int i=0 ; i<2 ; i++) {
BackupThread backupThread = new BackupThread(addNumberToKeepInRequest, backupKeepParamName);
BackupThread backupThread;
final String backupName = TestUtil.randomSimpleString(random(), 1, 20);
if(!namedBackup) {
backupThread = new BackupThread(addNumberToKeepInRequest, backupKeepParamName, ReplicationHandler.CMD_BACKUP);
} else {
backupThread = new BackupThread(backupName, ReplicationHandler.CMD_BACKUP);
backupNames[i] = backupName;
}
backupThread.start();
File dataDir = new File(master.getDataDir());
int waitCnt = 0;
CheckStatus checkStatus = new CheckStatus(firstBackupTimestamp);
CheckBackupStatus checkBackupStatus = new CheckBackupStatus(firstBackupTimestamp);
while(true) {
checkStatus.run();
if(checkStatus.fail != null) {
fail(checkStatus.fail);
checkBackupStatus.fetchStatus();
if(checkBackupStatus.fail != null) {
fail(checkBackupStatus.fail);
}
if(checkStatus.success) {
if(checkBackupStatus.success) {
if(i==0) {
firstBackupTimestamp = checkStatus.backupTimestamp;
firstBackupTimestamp = checkBackupStatus.backupTimestamp;
Thread.sleep(1000); //ensure the next backup will have a different timestamp.
}
break;
}
Thread.sleep(200);
if(waitCnt == 20) {
fail("Backup success not detected:" + checkStatus.response);
fail("Backup success not detected:" + checkBackupStatus.response);
}
waitCnt++;
}
@ -1435,9 +1486,9 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
if(backupThread.fail != null) {
fail(backupThread.fail);
}
File[] files = dataDir.listFiles(new FilenameFilter() {
File[] files = null;
if(!namedBackup) {
files = dataDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if(name.startsWith("snapshot")) {
@ -1446,6 +1497,17 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
return false;
}
});
} else {
files = dataDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if(name.startsWith("snapshot." + backupName)) {
return true;
}
return false;
}
});
}
assertEquals(1, files.length);
snapDir[i] = files[0];
Directory dir = new SimpleFSDirectory(snapDir[i].getAbsoluteFile());
@ -1455,11 +1517,39 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
assertEquals(nDocs, hits.totalHits);
reader.close();
dir.close();
if(!namedBackup && snapDir[0].exists()) {
fail("The first backup should have been cleaned up because " + backupKeepParamName + " was set to 1.");
}
}
if(snapDir[0].exists()) {
fail("The first backup should have been cleaned up because " + backupKeepParamName + " was set to 1.");
for(int i=0; i<2; i++) {
//Test Deletion of named backup
BackupThread deleteBackupThread = new BackupThread(backupNames[i], ReplicationHandler.CMD_DELETE_BACKUP);
deleteBackupThread.start();
int waitCnt = 0;
CheckDeleteBackupStatus checkDeleteBackupStatus = new CheckDeleteBackupStatus();
while(true) {
checkDeleteBackupStatus.fetchStatus();
if(checkDeleteBackupStatus.fail != null) {
fail(checkDeleteBackupStatus.fail);
}
if(checkDeleteBackupStatus.success) {
break;
}
Thread.sleep(200);
if(waitCnt == 20) {
fail("Delete Backup success not detected:" + checkDeleteBackupStatus.response);
}
waitCnt++;
}
if(deleteBackupThread.fail != null) {
fail(deleteBackupThread.fail);
}
}
//nocommit - Should move this to tearDown as it fails to delete the dir in case it fails?
for(int i=0 ; i< snapDir.length ; i++) {
AbstractSolrTestCase.recurseDelete(snapDir[i]); // clean up the snap dir
}