blob: 8925f0075faff2963947fd3396a3803303052bc3 [file] [log] [blame]
/**
* 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.hdfs.server.namenode;
import org.apache.hadoop.HadoopIllegalArgumentException;
import org.apache.hadoop.fs.InvalidPathException;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.hdfs.protocol.FSLimitException;
import org.apache.hadoop.hdfs.protocol.HdfsFileStatus;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReportListing;
import org.apache.hadoop.hdfs.protocol.SnapshotException;
import org.apache.hadoop.hdfs.protocol.SnapshottableDirectoryStatus;
import org.apache.hadoop.hdfs.protocol.SnapshotStatus;
import org.apache.hadoop.hdfs.server.namenode.FSDirectory.DirOp;
import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectorySnapshottableFeature;
import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotManager;
import org.apache.hadoop.hdfs.util.ReadOnlyList;
import org.apache.hadoop.util.ChunkedArrayList;
import org.apache.hadoop.util.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
class FSDirSnapshotOp {
public static final Logger LOG =
LoggerFactory.getLogger(FSDirSnapshotOp.class);
/** Verify if the snapshot name is legal. */
static void verifySnapshotName(FSDirectory fsd, String snapshotName,
String path)
throws FSLimitException.PathComponentTooLongException {
if (snapshotName.contains(Path.SEPARATOR)) {
throw new HadoopIllegalArgumentException(
"Snapshot name cannot contain \"" + Path.SEPARATOR + "\"");
}
final byte[] bytes = DFSUtil.string2Bytes(snapshotName);
fsd.verifyINodeName(bytes);
fsd.verifyMaxComponentLength(bytes, path);
}
/** Allow snapshot on a directory. */
static void allowSnapshot(FSDirectory fsd, SnapshotManager snapshotManager,
String path) throws IOException {
fsd.writeLock();
try {
snapshotManager.setSnapshottable(path, true);
} finally {
fsd.writeUnlock();
}
fsd.getEditLog().logAllowSnapshot(path);
}
static void disallowSnapshot(
FSDirectory fsd, SnapshotManager snapshotManager,
String path) throws IOException {
fsd.writeLock();
try {
snapshotManager.resetSnapshottable(path);
} finally {
fsd.writeUnlock();
}
fsd.getEditLog().logDisallowSnapshot(path);
}
/**
* Create a snapshot
* @param fsd FS directory
* @param pc FS permission checker
* @param snapshotRoot The directory path where the snapshot is taken
* @param snapshotName The name of the snapshot
* @param logRetryCache whether to record RPC ids in editlog for retry cache
* rebuilding.
*/
static String createSnapshot(
FSDirectory fsd, FSPermissionChecker pc, SnapshotManager snapshotManager,
String snapshotRoot, String snapshotName, boolean logRetryCache)
throws IOException {
final INodesInPath iip = fsd.resolvePath(pc, snapshotRoot, DirOp.WRITE);
if (fsd.isPermissionEnabled()) {
fsd.checkOwner(pc, iip);
}
if (snapshotName == null || snapshotName.isEmpty()) {
snapshotName = Snapshot.generateDefaultSnapshotName();
} else if (!DFSUtil.isValidNameForComponent(snapshotName)) {
throw new InvalidPathException("Invalid snapshot name: " + snapshotName);
}
String snapshotPath;
verifySnapshotName(fsd, snapshotName, snapshotRoot);
// time of snapshot creation
final long now = Time.now();
fsd.writeLock();
try {
snapshotPath = snapshotManager.createSnapshot(
fsd.getFSNamesystem().getLeaseManager(),
iip, snapshotRoot, snapshotName, now);
} finally {
fsd.writeUnlock();
}
fsd.getEditLog().logCreateSnapshot(snapshotRoot, snapshotName,
logRetryCache, now);
LOG.info("Created Snapshot for SnapshotRoot {}", snapshotRoot);
return snapshotPath;
}
static void renameSnapshot(FSDirectory fsd, FSPermissionChecker pc,
SnapshotManager snapshotManager, String path, String snapshotOldName,
String snapshotNewName, boolean logRetryCache) throws IOException {
final INodesInPath iip = fsd.resolvePath(pc, path, DirOp.WRITE);
if (fsd.isPermissionEnabled()) {
fsd.checkOwner(pc, iip);
}
verifySnapshotName(fsd, snapshotNewName, path);
// time of snapshot modification
final long now = Time.now();
fsd.writeLock();
try {
snapshotManager.renameSnapshot(iip, path, snapshotOldName,
snapshotNewName, now);
} finally {
fsd.writeUnlock();
}
fsd.getEditLog().logRenameSnapshot(path, snapshotOldName,
snapshotNewName, logRetryCache, now);
LOG.info("Snapshot renamed from {} to {} for SnapshotRoot {}",
snapshotOldName, snapshotNewName, path);
}
static SnapshottableDirectoryStatus[] getSnapshottableDirListing(
FSDirectory fsd, FSPermissionChecker pc, SnapshotManager snapshotManager)
throws IOException {
fsd.readLock();
try {
final String user = pc.isSuperUser()? null : pc.getUser();
return snapshotManager.getSnapshottableDirListing(user);
} finally {
fsd.readUnlock();
}
}
static SnapshotStatus[] getSnapshotListing(
FSDirectory fsd, FSPermissionChecker pc, SnapshotManager snapshotManager,
String path)
throws IOException {
fsd.readLock();
try {
INodesInPath iip = fsd.getINodesInPath(path, DirOp.READ);
if (fsd.isPermissionEnabled()) {
fsd.checkPathAccess(pc, iip, FsAction.READ);
}
return snapshotManager.getSnapshotListing(iip);
} finally {
fsd.readUnlock();
}
}
static SnapshotDiffReport getSnapshotDiffReport(FSDirectory fsd,
FSPermissionChecker pc, SnapshotManager snapshotManager, String path,
String fromSnapshot, String toSnapshot) throws IOException {
SnapshotDiffReport diffs;
fsd.readLock();
try {
INodesInPath iip = fsd.resolvePath(pc, path, DirOp.READ);
if (fsd.isPermissionEnabled()) {
checkSubtreeReadPermission(fsd, pc, path, fromSnapshot);
checkSubtreeReadPermission(fsd, pc, path, toSnapshot);
}
diffs = snapshotManager.diff(iip, path, fromSnapshot, toSnapshot);
} finally {
fsd.readUnlock();
}
return diffs;
}
static SnapshotDiffReportListing getSnapshotDiffReportListing(FSDirectory fsd,
FSPermissionChecker pc, SnapshotManager snapshotManager, String path,
String fromSnapshot, String toSnapshot, byte[] startPath, int index,
int snapshotDiffReportLimit) throws IOException {
SnapshotDiffReportListing diffs;
fsd.readLock();
try {
INodesInPath iip = fsd.resolvePath(pc, path, DirOp.READ);
if (fsd.isPermissionEnabled()) {
checkSubtreeReadPermission(fsd, pc, path, fromSnapshot);
checkSubtreeReadPermission(fsd, pc, path, toSnapshot);
}
diffs = snapshotManager
.diff(iip, path, fromSnapshot, toSnapshot, startPath, index,
snapshotDiffReportLimit);
} catch (Exception e) {
throw e;
} finally {
fsd.readUnlock();
}
return diffs;
}
/** Get a collection of full snapshot paths given file and snapshot dir.
* @param lsf a list of snapshottable features
* @param file full path of the file
* @return collection of full paths of snapshot of the file
*/
static Collection<String> getSnapshotFiles(FSDirectory fsd,
List<DirectorySnapshottableFeature> lsf,
String file) throws IOException {
ArrayList<String> snaps = new ArrayList<>();
for (DirectorySnapshottableFeature sf : lsf) {
// for each snapshottable dir e.g. /dir1, /dir2
final ReadOnlyList<Snapshot> lsnap = sf.getSnapshotList();
for (Snapshot s : lsnap) {
// for each snapshot name under snapshottable dir
// e.g. /dir1/.snapshot/s1, /dir1/.snapshot/s2
final String dirName = s.getRoot().getRootFullPathName();
if (!file.startsWith(dirName)) {
// file not in current snapshot root dir, no need to check other snaps
break;
}
String snapname = s.getRoot().getFullPathName();
if (dirName.equals(Path.SEPARATOR)) { // handle rootDir
snapname += Path.SEPARATOR;
}
snapname += file.substring(file.indexOf(dirName) + dirName.length());
HdfsFileStatus stat =
fsd.getFSNamesystem().getFileInfo(snapname, true, false, false);
if (stat != null) {
snaps.add(snapname);
}
}
}
return snaps;
}
/**
* Delete a snapshot of a snapshottable directory
* @param fsd The FS directory
* @param pc The permission checker
* @param snapshotManager The snapshot manager
* @param snapshotRoot The snapshottable directory
* @param snapshotName The name of the to-be-deleted snapshot
* @param logRetryCache whether to record RPC ids in editlog for retry cache
* rebuilding.
* @throws IOException
*/
static INode.BlocksMapUpdateInfo deleteSnapshot(
FSDirectory fsd, FSPermissionChecker pc, SnapshotManager snapshotManager,
String snapshotRoot, String snapshotName, boolean logRetryCache)
throws IOException {
final INodesInPath iip = fsd.resolvePath(pc, snapshotRoot, DirOp.WRITE);
if (fsd.isPermissionEnabled()) {
fsd.checkOwner(pc, iip);
}
// time of snapshot deletion
final long now = Time.now();
final INode.BlocksMapUpdateInfo collectedBlocks = deleteSnapshot(
fsd, snapshotManager, iip, snapshotName, now, snapshotRoot,
logRetryCache);
LOG.info("Snapshot {} deleted for SnapshotRoot {}",
snapshotName, snapshotName);
return collectedBlocks;
}
static INode.BlocksMapUpdateInfo deleteSnapshot(
FSDirectory fsd, SnapshotManager snapshotManager, INodesInPath iip,
String snapshotName, long now, String snapshotRoot, boolean logRetryCache)
throws IOException {
INode.BlocksMapUpdateInfo collectedBlocks = new INode.BlocksMapUpdateInfo();
ChunkedArrayList<INode> removedINodes = new ChunkedArrayList<>();
INode.ReclaimContext context = new INode.ReclaimContext(
fsd.getBlockStoragePolicySuite(), collectedBlocks, removedINodes, null);
fsd.writeLock();
try {
snapshotManager.deleteSnapshot(iip, snapshotName, context, now);
fsd.updateCount(iip, context.quotaDelta(), false);
fsd.removeFromInodeMap(removedINodes);
fsd.updateReplicationFactor(context.collectedBlocks()
.toUpdateReplicationInfo());
} finally {
fsd.writeUnlock();
}
removedINodes.clear();
fsd.getEditLog().logDeleteSnapshot(snapshotRoot, snapshotName,
logRetryCache, now);
return collectedBlocks;
}
private static void checkSubtreeReadPermission(
FSDirectory fsd, final FSPermissionChecker pc, String snapshottablePath,
String snapshot) throws IOException {
final String fromPath = snapshot == null ?
snapshottablePath : Snapshot.getSnapshotPath(snapshottablePath,
snapshot);
INodesInPath iip = fsd.resolvePath(pc, fromPath, DirOp.READ);
fsd.checkPermission(pc, iip, false, null, null, FsAction.READ,
FsAction.READ);
}
/**
* Check if the given INode (or one of its descendants) is snapshottable and
* already has snapshots.
*
* @param target The given INode
* @param snapshottableDirs The list of directories that are snapshottable
* but do not have snapshots yet
*/
private static void checkSnapshot(
INode target, List<INodeDirectory> snapshottableDirs)
throws SnapshotException {
if (target.isDirectory()) {
INodeDirectory targetDir = target.asDirectory();
DirectorySnapshottableFeature sf = targetDir
.getDirectorySnapshottableFeature();
if (sf != null) {
if (sf.getNumSnapshots() > 0) {
String fullPath = targetDir.getFullPathName();
throw new SnapshotException("The directory " + fullPath
+ " cannot be deleted since " + fullPath
+ " is snapshottable and already has snapshots");
} else {
if (snapshottableDirs != null) {
snapshottableDirs.add(targetDir);
}
}
}
for (INode child : targetDir.getChildrenList(Snapshot.CURRENT_STATE_ID)) {
checkSnapshot(child, snapshottableDirs);
}
}
}
/**
* Check if the given path (or one of its descendants) is snapshottable and
* already has snapshots.
*
* @param fsd the FSDirectory
* @param iip inodes of the path
* @param snapshottableDirs The list of directories that are snapshottable
* but do not have snapshots yet
*/
static void checkSnapshot(FSDirectory fsd, INodesInPath iip,
List<INodeDirectory> snapshottableDirs) throws SnapshotException {
// avoid the performance penalty of recursing the tree if snapshots
// are not in use
SnapshotManager sm = fsd.getFSNamesystem().getSnapshotManager();
if (sm.getNumSnapshottableDirs() > 0) {
checkSnapshot(iip.getLastINode(), snapshottableDirs);
}
}
}