log.info("Storage node shutdown, preparing to move datafiles");
List<String> originalDataDirectories = new LinkedList<String>();
List<String> createdDataDirectories = new LinkedList<String>();
ConfigEditor configEditor = getYamlConfigEditor();
try {
configEditor.load();
// Moving the data directory
List<String> dataFileDirectories = configEditor.getDataFileDirectories();
if(dataFileDirectories.size() > 1) {
// We do not support this scenario
log.error("More than one datadirectory configured for the StorageNode. This operation mode is not supported by this tool");
StringBuilder pathListBuilder = new StringBuilder();
for (String dataFileDir : dataFileDirectories) {
pathListBuilder.append(dataFileDir).append(", ");
}
result.setErrorMessage("Could not proceed with moving datafiles from " + pathListBuilder.toString() + "this tool does not support"
+ " multiple datafile paths.");
return result;
} else if(dataFileDirectories.size() == 1) {
String currentDataFileLocation = dataFileDirectories.get(0);
boolean dataFilesMoved = copyDataDirectoryIfChanged(currentDataFileLocation, newDataFileDirectory);
if(dataFilesMoved) {
originalDataDirectories.add(currentDataFileLocation);
createdDataDirectories.add(newDataFileDirectory);
List<String> newDataFileDirectories = new LinkedList<String>();
newDataFileDirectories.add(newDataFileDirectory);
configEditor.setDataFileDirectories(newDataFileDirectories);
}
}
// In theory we wouldn't need to copy these, as draining should empty these
String currentCommitLogDirectory = configEditor.getCommitLogDirectory();
boolean commitLogCopied = copyDataDirectoryIfChanged(currentCommitLogDirectory, newCommitLogDirectory);
if(commitLogCopied) {
originalDataDirectories.add(currentCommitLogDirectory);
createdDataDirectories.add(newCommitLogDirectory);
configEditor.setCommitLogDirectory(newCommitLogDirectory);
}
// Not so dangerous if we lose these, but lets try to keep them
String currentSavedCachesDirectory = configEditor.getSavedCachesDirectory();
boolean savedCachesCopied = copyDataDirectoryIfChanged(currentSavedCachesDirectory, newSavedCachesDirectory);
if(savedCachesCopied) {
originalDataDirectories.add(currentSavedCachesDirectory);
createdDataDirectories.add(newSavedCachesDirectory);
configEditor.setSavedCachesDirectory(newSavedCachesDirectory);
}
log.info(this + " datafiles have been moved. Restarting storage node...");
OperationResult startResult = startNode();
if (startResult.getErrorMessage() != null) {
log.error("Failed to restart storage node:\n" + startResult.getErrorMessage());
result.setErrorMessage("Failed to restart storage node:\n" + startResult.getErrorMessage());
// rollback here
configEditor.restore();
purgeDirectories(createdDataDirectories);
} else {
result.setSimpleResult("The storage node was succesfully updated.");
// Commit changes, remove old directories
configEditor.save(); // This can still throw an exception, in which case we need to rollback
purgeDirectories(originalDataDirectories);
}
return result;
} catch (ConfigEditorException e) {
log.error("There was an error while trying to update " + configEditor.getConfigFile(), e);
if (e.getCause() instanceof YAMLException) {
log.info("Attempting to restore " + configEditor.getConfigFile());
try {
configEditor.restore();
purgeDirectories(createdDataDirectories);
result.setErrorMessage("Failed to update configuration file [" + configEditor.getConfigFile() + "]: " +
ThrowableUtil.getAllMessages(e.getCause()));
} catch (ConfigEditorException e1) {
log.error("Failed to restore " + configEditor.getConfigFile() + ". A copy of the file prior to any modifications " +
"can be found at " + configEditor.getBackupFile());
result.setErrorMessage("There was an error updating [" + configEditor.getConfigFile() + "] and undoing the changes " +
"Failed. A copy of the file can be found at " + configEditor.getBackupFile() + ". See the " +
"agent logs for more details");
}
}
EmsConnection emsConnection = getEmsConnection();
EmsBean storageService = emsConnection.getBean("org.apache.cassandra.db:type=StorageService");
EmsAttribute attribute = storageService.getAttribute("OperationMode");
String operationMode = (String) attribute.refresh();
if (!operationMode.equals("NORMAL")) {
result.setErrorMessage("Bootstrapping " + getHost() + " failed. The StorageService is reporting " +
operationMode + " for its operation mode but it should be reporting NORMAL. The StorageService " +
"operation mode is not to be confused with the Storage Node operation mode.");
}
return result;
} catch (IOException e) {
log.error("Moving datafiles failed", e);
purgeDirectories(createdDataDirectories);
configEditor.restore();
result.setErrorMessage("Failed to move all the files to new destinations, " + e.getLocalizedMessage() + ". StorageService was left offline" +
", investigate before restarting the node");
// OperationResult startResult = startNode(); // return the StorageNode online, but what if IOException was out of diskspace?
return result;
}