/* * * * 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; } }