mirror of https://github.com/apache/lucene.git
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:
parent
af86a78df5
commit
4a84567693
|
@ -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
|
||||
---------------------
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue