/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.falcon.cleanup; import org.apache.commons.el.ExpressionEvaluatorImpl; import org.apache.falcon.FalconException; import org.apache.falcon.entity.ClusterHelper; import org.apache.falcon.entity.EntityUtil; import org.apache.falcon.entity.store.ConfigurationStore; import org.apache.falcon.entity.v0.AccessControlList; import org.apache.falcon.entity.v0.Entity; import org.apache.falcon.entity.v0.EntityType; import org.apache.falcon.entity.v0.Frequency; import org.apache.falcon.entity.v0.Frequency.TimeUnit; import org.apache.falcon.entity.v0.cluster.Cluster; import org.apache.falcon.expression.ExpressionHelper; import org.apache.falcon.hadoop.HadoopClientFactory; import org.apache.falcon.security.CurrentUser; import org.apache.falcon.util.DeploymentUtil; import org.apache.falcon.util.RuntimeProperties; import org.apache.falcon.util.StartupProperties; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.jsp.el.ELException; import javax.servlet.jsp.el.ExpressionEvaluator; import java.io.IOException; /** * Falcon cleanup handler for cleaning up work, temp and log files * left behind by falcon. */ public abstract class AbstractCleanupHandler { protected static final Logger LOG = LoggerFactory.getLogger(AbstractCleanupHandler.class); protected static final ConfigurationStore STORE = ConfigurationStore.get(); public static final ExpressionEvaluator EVALUATOR = new ExpressionEvaluatorImpl(); public static final ExpressionHelper RESOLVER = ExpressionHelper.get(); protected long getRetention(Entity entity, TimeUnit timeUnit) throws FalconException { String retention = getRetentionValue(timeUnit); try { return (Long) EVALUATOR.evaluate("${" + retention + "}", Long.class, RESOLVER, RESOLVER); } catch (ELException e) { throw new FalconException("Unable to evalue retention limit: " + retention + " for entity: " + entity.getName()); } } private String getRetentionValue(Frequency.TimeUnit timeunit) { String defaultValue; switch (timeunit) { case minutes: defaultValue = "hours(24)"; break; case hours: defaultValue = "days(3)"; break; case days: defaultValue = "days(12)"; break; case months: defaultValue = "months(3)"; break; default: defaultValue = "days(1)"; } return RuntimeProperties.get().getProperty("log.cleanup.frequency." + timeunit + ".retention", defaultValue); } protected FileStatus[] getAllLogs(FileSystem fs, Cluster cluster, Entity entity) throws FalconException { FileStatus[] paths; try { Path logPath = getLogPath(cluster, entity); paths = fs.globStatus(logPath); } catch (IOException e) { throw new FalconException(e); } return paths; } private Path getLogPath(Cluster cluster, Entity entity) { // logsPath = base log path + relative path return new Path(EntityUtil.getLogPath(cluster, entity), getRelativeLogPath()); } private FileSystem getFileSystemAsEntityOwner(Cluster cluster, Entity entity) throws FalconException { try { final AccessControlList acl = entity.getACL(); // To support backward compatibility, will only use the ACL owner only if present if (acl != null) { CurrentUser.authenticate(acl.getOwner()); // proxy user } return HadoopClientFactory.get().createProxiedFileSystem( ClusterHelper.getConfiguration(cluster)); } catch (Exception e) { throw new FalconException(e); } } protected void delete(String clusterName, Entity entity, long retention) throws FalconException { Cluster currentCluster = STORE.get(EntityType.CLUSTER, clusterName); if (!isClusterInCurrentColo(currentCluster.getColo())) { LOG.info("Ignoring cleanup for {}: {} in cluster: {} as this does not belong to current colo", entity.getEntityType(), entity.getName(), clusterName); return; } LOG.info("Cleaning up logs for {}: {} in cluster: {} with retention: {}", entity.getEntityType(), entity.getName(), clusterName, retention); FileSystem fs = getFileSystemAsEntityOwner(currentCluster, entity); FileStatus[] logs = getAllLogs(fs, currentCluster, entity); deleteInternal(fs, currentCluster, entity, retention, logs); } private void deleteInternal(FileSystem fs, Cluster cluster, Entity entity, long retention, FileStatus[] logs) throws FalconException { if (logs == null || logs.length == 0) { LOG.info("Nothing to delete for cluster: {}, entity: {}", cluster.getName(), entity.getName()); return; } long now = System.currentTimeMillis(); for (FileStatus log : logs) { if (now - log.getModificationTime() > retention) { try { boolean isDeleted = fs.delete(log.getPath(), true); LOG.error(isDeleted ? "Deleted path: {}" : "Unable to delete path: {}", log.getPath()); deleteParentIfEmpty(fs, log.getPath().getParent()); } catch (IOException e) { throw new FalconException(" Unable to delete log file : " + log.getPath() + " for entity " + entity.getName() + " for cluster: " + cluster.getName(), e); } } else { LOG.info("Retention limit: {} is less than modification {} for path: {}", retention, (now - log.getModificationTime()), log.getPath()); } } } private void deleteParentIfEmpty(FileSystem fs, Path parent) throws IOException { FileStatus[] files = fs.listStatus(parent); if (files != null && files.length == 0) { LOG.info("Parent path: {} is empty, deleting path", parent); fs.delete(parent, true); deleteParentIfEmpty(fs, parent.getParent()); } } public abstract void cleanup() throws FalconException; protected abstract String getRelativeLogPath(); protected boolean isClusterInCurrentColo(String colo) { final String currentColo = StartupProperties.get().getProperty("current.colo", "default"); return DeploymentUtil.isEmbeddedMode() || currentColo.equals(colo); } }