/*
* 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;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hbase.regionserver.wal.HLogFileSystem;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.util.FSUtils;
import org.apache.hadoop.hbase.util.Threads;
/**
* An abstraction of the underlying filesystem. This is used by other entities such as
* {@link HLogFileSystem}, to make calls to the underlying filesystem.
*
*/
public abstract class HBaseFileSystem {
public static final Log LOG = LogFactory.getLog(HBaseFileSystem.class);
/**
* In order to handle NN connectivity hiccups, one need to retry non-idempotent operation at the
* client level.
*/
protected static int hdfsClientRetriesNumber;
private static int baseSleepBeforeRetries;
private static final int DEFAULT_HDFS_CLIENT_RETRIES_NUMBER = 10;
private static final int DEFAULT_BASE_SLEEP_BEFORE_RETRIES = 1000;
// This static block is added for performance reasons. This is to ensure we are not checking
// in the method calls whether retry properties are set or not. Refer to HBase-8288 for more
// context.
static {
setRetryCounts(HBaseConfiguration.create());
}
/**
* Deletes a file. Assumes the user has already checked for this directory existence.
* @param fs
* @param dir
* @return true if the directory is deleted.
* @throws IOException
*/
public static boolean deleteFileFromFileSystem(FileSystem fs, Path dir)
throws IOException {
IOException lastIOE = null;
int i = 0;
do {
try {
return fs.delete(dir, false);
} catch (IOException ioe) {
lastIOE = ioe;
if (!fs.exists(dir)) return true;
// dir is there, retry deleting after some time.
sleepBeforeRetry("Delete File", i + 1);
}
} while (++i <= hdfsClientRetriesNumber);
throw new IOException("Exception in deleteFileFromFileSystem", lastIOE);
}
/**
* Deletes a directory. Assumes the user has already checked for this directory existence.
* @param fs
* @param dir
* @return true if the directory is deleted.
* @throws IOException
*/
public static boolean deleteDirFromFileSystem(FileSystem fs, Path dir)
throws IOException {
IOException lastIOE = null;
int i = 0;
do {
try {
return fs.delete(dir, true);
} catch (IOException ioe) {
lastIOE = ioe;
if (!fs.exists(dir)) return true;
// dir is there, retry deleting after some time.
sleepBeforeRetry("Delete Dir", i + 1);
}
} while (++i <= hdfsClientRetriesNumber);
throw new IOException("Exception in deleteDirFromFileSystem", lastIOE);
}
protected static void setRetryCounts(Configuration conf) {
hdfsClientRetriesNumber = conf.getInt("hdfs.client.retries.number",
DEFAULT_HDFS_CLIENT_RETRIES_NUMBER);
baseSleepBeforeRetries = conf.getInt("hdfs.client.sleep.before.retries",
DEFAULT_BASE_SLEEP_BEFORE_RETRIES);
}
/**
* Creates a directory for a filesystem and configuration object. Assumes the user has already
* checked for this directory existence.
* @param fs
* @param dir
* @return the result of fs.mkdirs(). In case underlying fs throws an IOException, it checks
* whether the directory exists or not, and returns true if it exists.
* @throws IOException
*/
public static boolean makeDirOnFileSystem(FileSystem fs, Path dir)
throws IOException {
int i = 0;
IOException lastIOE = null;
do {
try {
return fs.mkdirs(dir);
} catch (IOException ioe) {
lastIOE = ioe;
if (fs.exists(dir)) return true; // directory is present
sleepBeforeRetry("Create Directory", i+1);
}
} while (++i <= hdfsClientRetriesNumber);
throw new IOException("Exception in makeDirOnFileSystem", lastIOE);
}
/**
* Renames a directory. Assumes the user has already checked for this directory existence.
* @param fs
* @param src
* @param dst
* @return true if the directory is renamed.
* @throws IOException
*/
public static boolean renameDirForFileSystem(FileSystem fs, Path src, Path dst)
throws IOException {
IOException lastIOE = null;
int i = 0;
do {
try {
return fs.rename(src, dst);
} catch (IOException ioe) {
lastIOE = ioe;
if (!fs.exists(src) && fs.exists(dst)) return true;
// src is there, retry renaming after some time.
sleepBeforeRetry("Rename Directory", i + 1);
}
} while (++i <= hdfsClientRetriesNumber);
throw new IOException("Exception in renameDirForFileSystem", lastIOE);
}
/**
* Creates a path on the file system. Checks whether the path exists already or not, and use it
* for retrying in case underlying fs throws an exception.
* If the dir already exists and overwrite flag is false, the underlying FileSystem throws
* an IOE. It is not retried and the IOE is re-thrown to the caller.
* @param fs
* @param dir
* @param overwrite
* @return
* @throws IOException
*/
public static FSDataOutputStream createPathOnFileSystem(FileSystem fs, Path dir,
boolean overwrite) throws IOException {
int i = 0;
boolean existsBefore = fs.exists(dir);
IOException lastIOE = null;
do {
try {
return fs.create(dir, overwrite);
} catch (IOException ioe) {
lastIOE = ioe;
if (existsBefore && !overwrite) throw ioe;// a legitimate exception
sleepBeforeRetry("Create Path", i + 1);
}
} while (++i <= hdfsClientRetriesNumber);
throw new IOException("Exception in createPathOnFileSystem", lastIOE);
}
/**
* Creates the specified file with the given permission.
* If the dir already exists and the overwrite flag is false, underlying FileSystem throws
* an IOE. It is not retried and the IOE is re-thrown to the caller.
* @param fs
* @param path
* @param perm
* @param overwrite
* @return
* @throws IOException
*/
public static FSDataOutputStream createPathWithPermsOnFileSystem(FileSystem fs,
Path path, FsPermission perm, boolean overwrite) throws IOException {
int i = 0;
IOException lastIOE = null;
boolean existsBefore = fs.exists(path);
do {
try {
return fs.create(path, perm, overwrite, FSUtils.getDefaultBufferSize(fs),
FSUtils.getDefaultReplication(fs, path), FSUtils.getDefaultBlockSize(fs, path), null);
} catch (IOException ioe) {
lastIOE = ioe;
if (existsBefore && !overwrite) throw ioe;// a legitimate exception
sleepBeforeRetry("Create Path with Perms", i + 1);
}
} while (++i <= hdfsClientRetriesNumber);
throw new IOException("Exception in createPathWithPermsOnFileSystem", lastIOE);
}
/**
* Creates the file. Assumes the user has already checked for this file existence.
* @param fs
* @param dir
* @return result true if the file is created with this call, false otherwise.
* @throws IOException
*/
public static boolean createNewFileOnFileSystem(FileSystem fs, Path file)
throws IOException {
int i = 0;
IOException lastIOE = null;
do {
try {
return fs.createNewFile(file);
} catch (IOException ioe) {
lastIOE = ioe;
if (fs.exists(file)) return true; // file exists now, return true.
sleepBeforeRetry("Create NewFile", i + 1);
}
} while (++i <= hdfsClientRetriesNumber);
throw new IOException("Exception in createNewFileOnFileSystem", lastIOE);
}
/**
* sleeping logic for static methods; handles the interrupt exception. Keeping a static version
* for this to avoid re-looking for the integer values.
*/
protected static void sleepBeforeRetry(String msg, int sleepMultiplier) {
if (sleepMultiplier > hdfsClientRetriesNumber) {
LOG.warn(msg + ", retries exhausted");
return;
}
LOG.info(msg + ", sleeping " + baseSleepBeforeRetries + " times " + sleepMultiplier);
Threads.sleep(baseSleepBeforeRetries * sleepMultiplier);
}
/**
* rename the src path to dest path and set the dest path's modify time to current timestamp
*/
public static boolean renameAndSetModifyTime(final FileSystem fs, Path src, Path dest)
throws IOException {
// set the modify time for TimeToLive Cleaner
fs.setTimes(src, EnvironmentEdgeManager.currentTimeMillis(), -1);
return renameDirForFileSystem(fs, src, dest);
}
}