/* * Copyright © 2014-2015 Cask Data, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package co.cask.cdap.logging.write; import co.cask.cdap.common.io.Locations; import com.google.common.base.Throwables; import com.google.common.collect.HashMultimap; import com.google.common.collect.SetMultimap; import org.apache.twill.filesystem.Location; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.URI; import java.util.Set; /** * Handles log file retention. */ public final class LogCleanup implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(LogCleanup.class); private final FileMetaDataManager fileMetaDataManager; private final Location rootDir; private final String namespacesDir; private final long retentionDurationMs; public LogCleanup(FileMetaDataManager fileMetaDataManager, Location rootDir, String namespacesDir, long retentionDurationMs) { this.fileMetaDataManager = fileMetaDataManager; this.rootDir = rootDir; this.namespacesDir = namespacesDir; this.retentionDurationMs = retentionDurationMs; LOG.info("Log retention duration = {} ms", retentionDurationMs); } @Override public void run() { LOG.info("Running log cleanup..."); try { long tillTime = System.currentTimeMillis() - retentionDurationMs; final SetMultimap<String, Location> parentDirs = HashMultimap.create(); fileMetaDataManager.cleanMetaData(tillTime, new FileMetaDataManager.DeleteCallback() { @Override public void handle(Location location, String namespacedLogBaseDir) { try { if (location.exists()) { LOG.info("Deleting log file {}", location); location.delete(); } parentDirs.put(namespacedLogBaseDir, getParent(location)); } catch (IOException e) { LOG.error("Got exception when deleting path {}", location, e); throw Throwables.propagate(e); } } }); // Delete any empty parent dirs for (String namespacedLogBaseDir : parentDirs.keySet()) { Set<Location> locations = parentDirs.get(namespacedLogBaseDir); for (Location location : locations) { deleteEmptyDir(namespacedLogBaseDir, location); } } } catch (Throwable e) { LOG.error("Got exception when cleaning up. Will try again later.", e); } } Location getParent(Location location) { Location parent = Locations.getParent(location); return (parent == null) ? location : parent; } /** * For the specified directory to be deleted, finds its namespaced log location, then deletes * @param namespacedLogBaseDir namespaced log base dir without the root dir prefixed * @param dir dir to delete * @throws IOException */ void deleteEmptyDir(String namespacedLogBaseDir, Location dir) throws IOException { LOG.debug("Got path {}", dir); Location namespacedLogBaseLocation = rootDir.append(namespacesDir).append(namespacedLogBaseDir); deleteEmptyDirsInNamespace(namespacedLogBaseLocation, dir); } /** * Given a namespaced log dir - e.g. /{root}/ns1/logs, deletes dir if it is empty, and recursively deletes parent dirs * if they are empty too. The recursion stops at non-empty parent or the specified namespaced log base directory. * If dir is not child of base directory then the recursion stops at root. * @param dirToDelete dir to be deleted. */ private void deleteEmptyDirsInNamespace(Location namespacedLogBaseDir, Location dirToDelete) { // Don't delete a dir if it is equal to or a parent of logBaseDir URI namespacedLogBaseURI = namespacedLogBaseDir.toURI(); URI dirToDeleteURI = dirToDelete.toURI(); if (namespacedLogBaseURI.equals(dirToDeleteURI) || !dirToDeleteURI.getRawPath().startsWith(namespacedLogBaseURI.getRawPath())) { LOG.debug("{} not deletion candidate.", dirToDelete); return; } try { if (dirToDelete.list().isEmpty() && dirToDelete.delete()) { LOG.info("Deleted empty dir {}", dirToDelete); // See if parent dir is empty, and needs deleting Location parent = getParent(dirToDelete); LOG.debug("Deleting parent dir {}", parent); deleteEmptyDirsInNamespace(namespacedLogBaseDir, parent); } else { LOG.debug("Not deleting non-dir or non-empty dir {}", dirToDelete); } } catch (IOException e) { LOG.error("Got exception while deleting dir {}", dirToDelete, e); } } }