/*
*
* * RHQ Management Platform
* * Copyright (C) 2005-2014 Red Hat, Inc.
* * All rights reserved.
* *
* * This program is free software; you can redistribute it and/or modify
* * it under the terms of the GNU General Public License, version 2, as
* * published by the Free Software Foundation, and/or the GNU Lesser
* * General Public License, version 2.1, also as published by the Free
* * Software Foundation.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License and the GNU Lesser General Public License
* * for more details.
* *
* * You should have received a copy of the GNU General Public License
* * and the GNU Lesser General Public License along with this program;
* * if not, write to the Free Software Foundation, Inc.,
* * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
package org.rhq.plugins.cassandra.util;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.pluginapi.operation.OperationResult;
public class TakeSnapshotOperation {
private final String R_STRATEGY_KEEP_ALL = "Keep All";
private final String R_STRATEGY_KEEP_LASTN = "Keep Last N";
private final String R_STRATEGY_DEL_OLDERN = "Delete Older Than N days";
private final String D_STRATEGY_DEL = "Delete";
private final String D_STRATEGY_MOVE = "Move";
private final KeyspaceService service;
private final Configuration parameters;
private final Log log = LogFactory.getLog(TakeSnapshotOperation.class);
public TakeSnapshotOperation(KeyspaceService service, Configuration parameters) {
this.service = service;
this.parameters = parameters;
}
public OperationResult invoke() {
OperationResult result = new OperationResult();
StringBuilder sbResult = new StringBuilder();
String snapshotName = parameters.getSimpleValue("snapshotName", "" + System.currentTimeMillis()).trim();
if (snapshotName.isEmpty()) {
result.setErrorMessage("Snapshot Name parameter cannot be an empty string");
return result;
}
String retentionStrategy = parameters.getSimpleValue("retentionStrategy", R_STRATEGY_KEEP_ALL);
Integer count = parameters.getSimple("count").getIntegerValue();
String deletionStrategy = parameters.getSimpleValue("deletionStrategy", D_STRATEGY_DEL);
String location = parameters.getSimpleValue("location");
// validate parameters
if (R_STRATEGY_KEEP_LASTN.equals(retentionStrategy) || R_STRATEGY_DEL_OLDERN.equals(retentionStrategy)) {
if (count == null) {
result.setErrorMessage("Invalid input parameters. Selected Retention Strategy [" + retentionStrategy
+ "] but 'count' parameter was null");
return result;
}
}
if (D_STRATEGY_MOVE.equals(deletionStrategy)) {
if (location == null) {
result.setErrorMessage("Invalid input parameters. Selected Deletion Strategy [" + deletionStrategy
+ "] but 'location' parameter was null");
return result;
}
}
String[] keyspaces = service.getKeyspaces().toArray(new String[] {});
log.info("Taking snapshot of keyspaces " + Arrays.toString(keyspaces));
long startTime = System.currentTimeMillis();
service.takeSnapshot(keyspaces, snapshotName);
log.info("Snapshot taken in " + (System.currentTimeMillis() - startTime) + "ms");
sbResult.append("Snapshot [" + snapshotName + "] was successfully created.");
if (R_STRATEGY_KEEP_ALL.equals(retentionStrategy)) { // do nothing
result.setSimpleResult(sbResult.toString());
return result;
}
// verify if we can write to location to move older snapshots
if (D_STRATEGY_MOVE.equals(deletionStrategy)) {
File locationDir = new File(location);
if (!locationDir.exists()) {
try {
if (!locationDir.mkdirs()) {
result.setErrorMessage("Location [" + locationDir.getAbsolutePath()
+ "] did not exist and failed to be created");
result.setSimpleResult(sbResult.toString());
return result;
}
} catch (Exception e) {
result.setErrorMessage("Location [" + locationDir.getAbsolutePath()
+ "] did not exist and failed to be created - " + e.getMessage());
result.setSimpleResult(sbResult.toString());
return result;
}
}
if (!locationDir.isDirectory()) {
result.setErrorMessage("Location [" + locationDir.getAbsolutePath() + "] must be directory");
result.setSimpleResult(sbResult.toString());
return result;
}
if (!locationDir.canWrite()) {
result.setErrorMessage("Location [" + locationDir.getAbsolutePath() + "] must be writable");
result.setSimpleResult(sbResult.toString());
return result;
}
}
List<String> columnFamilyDirs = service.getColumnFamilyDirs();
// obtain list of snapshot dirs to be moved or deleted
List<String[]> eligibleSnapshots = findEligibleSnapshots(service.getKeySpaceDataFileLocations(),
columnFamilyDirs, retentionStrategy, count);
if (eligibleSnapshots.isEmpty()) {
return result;
}
StringBuilder sbMoveOrDeleteErrors = new StringBuilder();
if (D_STRATEGY_DEL.equals(deletionStrategy)) {
log.info("Strategy [" + deletionStrategy + "] is set, deleting " + eligibleSnapshots.size() + " snapshots");
sbResult.append("\nDeleting " + eligibleSnapshots.size() + " directories.");
int deleted = 0;
for (String[] snapPath : eligibleSnapshots) {
File snapDir = new File(snapPath[0], snapPath[1]);
log.info("Deleting " + snapDir);
if (!FileUtils.deleteQuietly(snapDir)) {
log.warn("Failed to delete " + snapDir.getAbsolutePath());
sbMoveOrDeleteErrors.append("Failed to delete [" + snapDir.getAbsolutePath() + "]\n ");
} else {
deleted++;
}
}
sbResult.append("\nSuccessfully deleted " + deleted + " directories");
}
if (D_STRATEGY_MOVE.equals(deletionStrategy)) {
log.info("Strategy [" + deletionStrategy + "] is set, moving " + eligibleSnapshots.size() + " snapshots");
sbResult.append("\nMoving " + eligibleSnapshots.size() + " directories to [" + location + "].");
int moved = 0;
for (String[] snapPath : eligibleSnapshots) {
File snapDir = new File(snapPath[0], snapPath[1]);
File snapTargetDir = new File(location, snapPath[1]);
log.info("Moving " + snapDir + " to " + snapTargetDir);
try {
FileUtils.moveDirectoryToDirectory(snapDir, snapTargetDir.getParentFile(), true);
moved++;
} catch (IOException e) {
log.warn("Failed to move directory : " + e.getMessage());
sbMoveOrDeleteErrors.append("Failed to move [" + snapDir.getAbsolutePath() + "] to ["
+ snapTargetDir.getAbsolutePath() + "]\n ");
}
}
sbResult.append("\nSuccessfully moved " + moved + " directories.");
}
if (sbMoveOrDeleteErrors.length() > 0) {
result.setErrorMessage(sbMoveOrDeleteErrors.toString());
}
result.setSimpleResult(sbResult.toString());
return result;
}
/**
* find eligible snapshot dirs - to be deleted or moved. Dirs are returned as pairs, we need this - in case 'Move' is required we'll have to create
* snapshot subdirectory relative to target location
* @param dataDirs root dataDirs
* @param colFamilyDirs relative paths to dataDirs
* @param retentionStrategy
* @param count
* @return list of pairs <dataDir,snapShotDir>
*/
private List<String[]> findEligibleSnapshots(String[] dataDirs, List<String> colFamilyDirs,
String retentionStrategy, Integer count) {
List<String[]> eligibleSnapshots = new ArrayList<String[]>();
for (String dataRoot : dataDirs) {
for (String keySpace : colFamilyDirs) {
String colFamilyDir = keySpace + File.separator + "snapshots";
File keySpaceDir = new File(dataRoot, colFamilyDir);
if (keySpaceDir.exists()) { // might not exist in case there are several dataRoots
File[] snapshots = keySpaceDir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory();
}
});
// sort, newest first
Arrays.sort(snapshots, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.lastModified() > o2.lastModified() ? -1 : 1;
}
});
if (R_STRATEGY_KEEP_LASTN.equals(retentionStrategy)) {
int index = 0;
for (File f : snapshots) {
if (index >= count) {
eligibleSnapshots.add(new String[] { dataRoot,
colFamilyDir + File.separator + f.getName() });
}
index++;
}
}
if (R_STRATEGY_DEL_OLDERN.equals(retentionStrategy)) {
long cutOff = System.currentTimeMillis() - (count * 86400L * 1000L);
for (File f : snapshots) {
if (f.lastModified() < cutOff) {
eligibleSnapshots.add(new String[] { dataRoot,
colFamilyDir + File.separator + f.getName() });
}
}
}
}
}
}
return eligibleSnapshots;
}
}