org.apache.hadoop.hbase.master.cleaner.CleanerChore.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.master.cleaner.CleanerChore.java

Source

/**
 * 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.hadoop.hbase.master.cleaner;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.Chore;
import org.apache.hadoop.hbase.RemoteExceptionHandler;
import org.apache.hadoop.hbase.Stoppable;
import org.apache.hadoop.hbase.util.FSUtils;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

/**
 * Abstract Cleaner that uses a chain of delegates to clean a directory of files
 * @param <T> Cleaner delegate class that is dynamically loaded from configuration
 */
public abstract class CleanerChore<T extends FileCleanerDelegate> extends Chore {

    private static final Log LOG = LogFactory.getLog(CleanerChore.class.getName());

    private final FileSystem fs;
    private final Path oldFileDir;
    private final Configuration conf;
    protected List<T> cleanersChain;

    /**
     * @param name name of the chore being run
     * @param sleepPeriod the period of time to sleep between each run
     * @param s the stopper
     * @param conf configuration to use
     * @param fs handle to the FS
     * @param oldFileDir the path to the archived files
     * @param confKey configuration key for the classes to instantiate
     */
    public CleanerChore(String name, final int sleepPeriod, final Stoppable s, Configuration conf, FileSystem fs,
            Path oldFileDir, String confKey) {
        super(name, sleepPeriod, s);
        this.fs = fs;
        this.oldFileDir = oldFileDir;
        this.conf = conf;

        initCleanerChain(confKey);
    }

    /**
     * Validate the file to see if it even belongs in the directory. If it is valid, then the file
     * will go through the cleaner delegates, but otherwise the file is just deleted.
     * @param file full {@link Path} of the file to be checked
     * @return <tt>true</tt> if the file is valid, <tt>false</tt> otherwise
     */
    protected abstract boolean validate(Path file);

    /**
     * Instantiate and initialize all the file cleaners set in the configuration
     * @param confKey key to get the file cleaner classes from the configuration
     */
    private void initCleanerChain(String confKey) {
        this.cleanersChain = new LinkedList<T>();
        String[] logCleaners = conf.getStrings(confKey);
        if (logCleaners != null) {
            for (String className : logCleaners) {
                T logCleaner = newFileCleaner(className, conf);
                if (logCleaner != null) {
                    LOG.debug("initialize cleaner=" + className);
                    this.cleanersChain.add(logCleaner);
                }
            }
        }
    }

    /**
     * A utility method to create new instances of LogCleanerDelegate based on the class name of the
     * LogCleanerDelegate.
     * @param className fully qualified class name of the LogCleanerDelegate
     * @param conf
     * @return the new instance
     */
    private T newFileCleaner(String className, Configuration conf) {
        try {
            Class<? extends FileCleanerDelegate> c = Class.forName(className).asSubclass(FileCleanerDelegate.class);
            @SuppressWarnings("unchecked")
            T cleaner = (T) c.newInstance();
            cleaner.setConf(conf);
            return cleaner;
        } catch (Exception e) {
            LOG.warn("Can NOT create CleanerDelegate: " + className, e);
            // skipping if can't instantiate
            return null;
        }
    }

    @Override
    protected void chore() {
        try {
            FileStatus[] files = FSUtils.listStatus(this.fs, this.oldFileDir);
            checkAndDeleteEntries(files);
        } catch (IOException e) {
            e = RemoteExceptionHandler.checkIOException(e);
            LOG.warn("Error while cleaning the logs", e);
        }
    }

    /**
     * Loop over the given directory entries, and check whether they can be deleted.
     * If an entry is itself a directory it will be recursively checked and deleted itself iff
     * all subentries are deleted (and no new subentries are added in the mean time)
     *
     * @param entries directory entries to check
     * @return true if all entries were successfully deleted
     */
    private boolean checkAndDeleteEntries(FileStatus[] entries) {
        if (entries == null) {
            return true;
        }
        boolean allEntriesDeleted = true;
        List<FileStatus> files = Lists.newArrayListWithCapacity(entries.length);
        for (FileStatus child : entries) {
            Path path = child.getPath();
            if (child.isDirectory()) {
                // for each subdirectory delete it and all entries if possible
                if (!checkAndDeleteDirectory(path)) {
                    allEntriesDeleted = false;
                }
            } else {
                // collect all files to attempt to delete in one batch
                files.add(child);
            }
        }
        if (!checkAndDeleteFiles(files)) {
            allEntriesDeleted = false;
        }
        return allEntriesDeleted;
    }

    /**
     * Attempt to delete a directory and all files under that directory. Each child file is passed
     * through the delegates to see if it can be deleted. If the directory has no children when the
     * cleaners have finished it is deleted.
     * <p>
     * If new children files are added between checks of the directory, the directory will <b>not</b>
     * be deleted.
     * @param dir directory to check
     * @return <tt>true</tt> if the directory was deleted, <tt>false</tt> otherwise.
     */
    @VisibleForTesting
    boolean checkAndDeleteDirectory(Path dir) {
        if (LOG.isTraceEnabled()) {
            LOG.trace("Checking directory: " + dir);
        }

        try {
            FileStatus[] children = FSUtils.listStatus(fs, dir);
            boolean allChildrenDeleted = checkAndDeleteEntries(children);

            // if the directory still has children, we can't delete it, so we are done
            if (!allChildrenDeleted)
                return false;
        } catch (IOException e) {
            e = RemoteExceptionHandler.checkIOException(e);
            LOG.warn("Error while listing directory: " + dir, e);
            // couldn't list directory, so don't try to delete, and don't return success
            return false;
        }

        // otherwise, all the children (that we know about) have been deleted, so we should try to
        // delete this directory. However, don't do so recursively so we don't delete files that have
        // been added since we last checked.
        try {
            return fs.delete(dir, false);
        } catch (IOException e) {
            if (LOG.isTraceEnabled()) {
                LOG.trace("Couldn't delete directory: " + dir, e);
            }
            // couldn't delete w/o exception, so we can't return success.
            return false;
        }
    }

    /**
     * Run the given files through each of the cleaners to see if it should be deleted, deleting it if
     * necessary.
     * @param files List of FileStatus for the files to check (and possibly delete)
     * @return true iff successfully deleted all files
     */
    private boolean checkAndDeleteFiles(List<FileStatus> files) {
        // first check to see if the path is valid
        List<FileStatus> validFiles = Lists.newArrayListWithCapacity(files.size());
        List<FileStatus> invalidFiles = Lists.newArrayList();
        for (FileStatus file : files) {
            if (validate(file.getPath())) {
                validFiles.add(file);
            } else {
                LOG.warn("Found a wrongly formatted file: " + file.getPath() + " - will delete it.");
                invalidFiles.add(file);
            }
        }

        Iterable<FileStatus> deletableValidFiles = validFiles;
        // check each of the cleaners for the valid files
        for (T cleaner : cleanersChain) {
            if (cleaner.isStopped() || this.stopper.isStopped()) {
                LOG.warn("A file cleaner" + this.getName() + " is stopped, won't delete any more files in:"
                        + this.oldFileDir);
                return false;
            }

            Iterable<FileStatus> filteredFiles = cleaner.getDeletableFiles(deletableValidFiles);

            // trace which cleaner is holding on to each file
            if (LOG.isTraceEnabled()) {
                ImmutableSet<FileStatus> filteredFileSet = ImmutableSet.copyOf(filteredFiles);
                for (FileStatus file : deletableValidFiles) {
                    if (!filteredFileSet.contains(file)) {
                        LOG.trace(file.getPath() + " is not deletable according to:" + cleaner);
                    }
                }
            }

            deletableValidFiles = filteredFiles;
        }

        Iterable<FileStatus> filesToDelete = Iterables.concat(invalidFiles, deletableValidFiles);
        int deletedFileCount = 0;
        for (FileStatus file : filesToDelete) {
            Path filePath = file.getPath();
            if (LOG.isTraceEnabled()) {
                LOG.trace("Removing: " + filePath + " from archive");
            }
            try {
                boolean success = this.fs.delete(filePath, false);
                if (success) {
                    deletedFileCount++;
                } else {
                    LOG.warn("Attempted to delete:" + filePath
                            + ", but couldn't. Run cleaner chain and attempt to delete on next pass.");
                }
            } catch (IOException e) {
                e = RemoteExceptionHandler.checkIOException(e);
                LOG.warn("Error while deleting: " + filePath, e);
            }
        }

        return deletedFileCount == files.size();
    }

    @Override
    public void cleanup() {
        for (T lc : this.cleanersChain) {
            try {
                lc.stop("Exiting");
            } catch (Throwable t) {
                LOG.warn("Stopping", t);
            }
        }
    }
}