blob: f9ab3945a03ebf1588bbf480aba6d848863c398c [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.snapshot;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_CAPTURE_OPENFILES;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_CAPTURE_OPENFILES_DEFAULT;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_SKIP_CAPTURE_ACCESSTIME_ONLY_CHANGE;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_SKIP_CAPTURE_ACCESSTIME_ONLY_CHANGE_DEFAULT;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.management.ObjectName;
import org.apache.hadoop.thirdparty.com.google.common.annotations.VisibleForTesting;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.XAttr;
import org.apache.hadoop.fs.XAttrSetFlag;
import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.hdfs.DFSUtilClient;
import org.apache.hadoop.hdfs.XAttrHelper;
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.SnapshotInfo;
import org.apache.hadoop.hdfs.protocol.SnapshottableDirectoryStatus;
import org.apache.hadoop.hdfs.protocol.SnapshotStatus;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffReportEntry;
import org.apache.hadoop.hdfs.server.common.HdfsServerConstants;
import org.apache.hadoop.hdfs.server.namenode.*;
import org.apache.hadoop.hdfs.server.namenode.FSDirectory.DirOp;
import org.apache.hadoop.hdfs.server.namenode.FSImageFormat;
import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
import org.apache.hadoop.hdfs.server.namenode.INode;
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
import org.apache.hadoop.hdfs.server.namenode.INodesInPath;
import org.apache.hadoop.hdfs.server.namenode.LeaseManager;
import org.apache.hadoop.hdfs.util.ReadOnlyList;
import org.apache.hadoop.metrics2.util.MBeans;
import org.apache.hadoop.util.Lists;
import org.apache.hadoop.thirdparty.com.google.common.base.Preconditions;
import org.apache.hadoop.util.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manage snapshottable directories and their snapshots.
*
* This class includes operations that create, access, modify snapshots and/or
* snapshot-related data. In general, the locking structure of snapshot
* operations is: <br>
*
* 1. Lock the {@link FSNamesystem} lock in {@link FSNamesystem} before calling
* into {@link SnapshotManager} methods.<br>
* 2. Lock the {@link FSDirectory} lock for the {@link SnapshotManager} methods
* if necessary.
*/
public class SnapshotManager implements SnapshotStatsMXBean {
public static final Logger LOG =
LoggerFactory.getLogger(SnapshotManager.class);
// The following are private configurations
static final String DFS_NAMENODE_SNAPSHOT_DELETION_ORDERED
= "dfs.namenode.snapshot.deletion.ordered";
static final boolean DFS_NAMENODE_SNAPSHOT_DELETION_ORDERED_DEFAULT
= false;
static final String DFS_NAMENODE_SNAPSHOT_DELETION_ORDERED_GC_PERIOD_MS
= "dfs.namenode.snapshot.deletion.ordered.gc.period.ms";
static final long DFS_NAMENODE_SNAPSHOT_DELETION_ORDERED_GC_PERIOD_MS_DEFAULT
= 5 * 60_000L; //5 minutes
private static final ThreadLocal<Boolean> DELETION_ORDERED
= new ThreadLocal<>();
static boolean isDeletionOrdered() {
final Boolean b = DELETION_ORDERED.get();
return b != null? b: false;
}
public void initThreadLocals() {
DELETION_ORDERED.set(isSnapshotDeletionOrdered());
}
private final FSDirectory fsdir;
private boolean captureOpenFiles;
/**
* If skipCaptureAccessTimeOnlyChange is set to true, if accessTime
* of a file changed but there is no other modification made to the file,
* it will not be captured in next snapshot. However, if there is other
* modification made to the file, the last access time will be captured
* together with the modification in next snapshot.
*/
private boolean skipCaptureAccessTimeOnlyChange = false;
/**
* If snapshotDiffAllowSnapRootDescendant is set to true, snapshot diff
* operation can be run for any descendant directory under a snapshot root
* directory and the diff calculation will be scoped to the descendant
* directory.
*/
private final boolean snapshotDiffAllowSnapRootDescendant;
private final AtomicInteger numSnapshots = new AtomicInteger();
private static final int SNAPSHOT_ID_BIT_WIDTH = 28;
private boolean allowNestedSnapshots = false;
private final boolean snapshotDeletionOrdered;
private int snapshotCounter = 0;
private final int maxSnapshotLimit;
private final int maxSnapshotFSLimit;
/** All snapshottable directories in the namesystem. */
private final Map<Long, INodeDirectory> snapshottables =
new ConcurrentHashMap<>();
public SnapshotManager(final Configuration conf, final FSDirectory fsdir)
throws SnapshotException {
this.fsdir = fsdir;
this.captureOpenFiles = conf.getBoolean(
DFS_NAMENODE_SNAPSHOT_CAPTURE_OPENFILES,
DFS_NAMENODE_SNAPSHOT_CAPTURE_OPENFILES_DEFAULT);
this.skipCaptureAccessTimeOnlyChange = conf.getBoolean(
DFS_NAMENODE_SNAPSHOT_SKIP_CAPTURE_ACCESSTIME_ONLY_CHANGE,
DFS_NAMENODE_SNAPSHOT_SKIP_CAPTURE_ACCESSTIME_ONLY_CHANGE_DEFAULT);
this.snapshotDiffAllowSnapRootDescendant = conf.getBoolean(
DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_DIFF_ALLOW_SNAP_ROOT_DESCENDANT,
DFSConfigKeys.
DFS_NAMENODE_SNAPSHOT_DIFF_ALLOW_SNAP_ROOT_DESCENDANT_DEFAULT);
this.maxSnapshotLimit = conf.getInt(
DFSConfigKeys.
DFS_NAMENODE_SNAPSHOT_MAX_LIMIT,
DFSConfigKeys.
DFS_NAMENODE_SNAPSHOT_MAX_LIMIT_DEFAULT);
this.maxSnapshotFSLimit = conf.getInt(
DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_FILESYSTEM_LIMIT,
DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_FILESYSTEM_LIMIT_DEFAULT);
LOG.info("Loaded config captureOpenFiles: " + captureOpenFiles
+ ", skipCaptureAccessTimeOnlyChange: "
+ skipCaptureAccessTimeOnlyChange
+ ", snapshotDiffAllowSnapRootDescendant: "
+ snapshotDiffAllowSnapRootDescendant
+ ", maxSnapshotFSLimit: "
+ maxSnapshotFSLimit
+ ", maxSnapshotLimit: "
+ maxSnapshotLimit);
this.snapshotDeletionOrdered = conf.getBoolean(
DFS_NAMENODE_SNAPSHOT_DELETION_ORDERED,
DFS_NAMENODE_SNAPSHOT_DELETION_ORDERED_DEFAULT);
LOG.info("{} = {}", DFS_NAMENODE_SNAPSHOT_DELETION_ORDERED,
snapshotDeletionOrdered);
final int maxLevels = conf.getInt(
DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_SKIPLIST_MAX_LEVELS,
DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_SKIPLIST_MAX_SKIP_LEVELS_DEFAULT);
final int skipInterval = conf.getInt(
DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_SKIPLIST_SKIP_INTERVAL,
DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_SKIPLIST_SKIP_INTERVAL_DEFAULT);
if (maxSnapshotLimit > maxSnapshotFSLimit) {
final String errMsg = DFSConfigKeys.
DFS_NAMENODE_SNAPSHOT_MAX_LIMIT
+ " cannot be greater than " +
DFSConfigKeys.DFS_NAMENODE_SNAPSHOT_FILESYSTEM_LIMIT;
throw new SnapshotException(errMsg);
}
DirectoryDiffListFactory.init(skipInterval, maxLevels, LOG);
}
public boolean isSnapshotDeletionOrdered() {
return snapshotDeletionOrdered;
}
@VisibleForTesting
void setCaptureOpenFiles(boolean captureOpenFiles) {
this.captureOpenFiles = captureOpenFiles;
}
/**
* @return skipCaptureAccessTimeOnlyChange
*/
public boolean getSkipCaptureAccessTimeOnlyChange() {
return skipCaptureAccessTimeOnlyChange;
}
/** Used in tests only */
void setAllowNestedSnapshots(boolean allowNestedSnapshots) {
this.allowNestedSnapshots = allowNestedSnapshots;
}
public boolean isAllowNestedSnapshots() {
return allowNestedSnapshots;
}
private void checkNestedSnapshottable(INodeDirectory dir, String path)
throws SnapshotException {
if (allowNestedSnapshots) {
return;
}
for(INodeDirectory s : snapshottables.values()) {
if (s.isAncestorDirectory(dir)) {
throw new SnapshotException(
"Nested snapshottable directories not allowed: path=" + path
+ ", the subdirectory " + s.getFullPathName()
+ " is already a snapshottable directory.");
}
if (dir.isAncestorDirectory(s)) {
throw new SnapshotException(
"Nested snapshottable directories not allowed: path=" + path
+ ", the ancestor " + s.getFullPathName()
+ " is already a snapshottable directory.");
}
}
}
/**
* Set the given directory as a snapshottable directory.
* If the path is already a snapshottable directory, update the quota.
*/
public void setSnapshottable(final String path, boolean checkNestedSnapshottable)
throws IOException {
final INodesInPath iip = fsdir.getINodesInPath(path, DirOp.WRITE);
final INodeDirectory d = INodeDirectory.valueOf(iip.getLastINode(), path);
if (checkNestedSnapshottable) {
checkNestedSnapshottable(d, path);
}
if (d.isSnapshottable()) {
//The directory is already a snapshottable directory.
d.setSnapshotQuota(DirectorySnapshottableFeature.SNAPSHOT_QUOTA_DEFAULT);
} else {
d.addSnapshottableFeature();
}
addSnapshottable(d);
}
/** Add the given snapshottable directory to {@link #snapshottables}. */
public void addSnapshottable(INodeDirectory dir) {
Preconditions.checkArgument(dir.isSnapshottable());
snapshottables.put(dir.getId(), dir);
}
/** Remove the given snapshottable directory from {@link #snapshottables}. */
private void removeSnapshottable(INodeDirectory s) {
snapshottables.remove(s.getId());
}
/** Remove snapshottable directories from {@link #snapshottables} */
public void removeSnapshottable(List<INodeDirectory> toRemove) {
if (toRemove != null) {
for (INodeDirectory s : toRemove) {
removeSnapshottable(s);
}
}
}
/**
* Set the given snapshottable directory to non-snapshottable.
*
* @throws SnapshotException if there are snapshots in the directory.
*/
public void resetSnapshottable(final String path) throws IOException {
final INodesInPath iip = fsdir.getINodesInPath(path, DirOp.WRITE);
final INodeDirectory d = INodeDirectory.valueOf(iip.getLastINode(), path);
DirectorySnapshottableFeature sf = d.getDirectorySnapshottableFeature();
if (sf == null) {
// the directory is already non-snapshottable
return;
}
if (sf.getNumSnapshots() > 0) {
throw new SnapshotException("The directory " + path + " has snapshot(s). "
+ "Please redo the operation after removing all the snapshots.");
}
if (d == fsdir.getRoot()) {
d.setSnapshotQuota(0);
} else {
d.removeSnapshottableFeature();
}
removeSnapshottable(d);
}
/**
* Find the source root directory where the snapshot will be taken
* for a given path.
*
* @return Snapshottable directory.
* @throws IOException
* Throw IOException when the given path does not lead to an
* existing snapshottable directory.
*/
public INodeDirectory getSnapshottableRoot(final INodesInPath iip)
throws IOException {
final String path = iip.getPath();
final INodeDirectory dir = INodeDirectory.valueOf(iip.getLastINode(), path);
if (!dir.isSnapshottable()) {
throw new SnapshotException(
"Directory is not a snapshottable directory: " + path);
}
return dir;
}
public void assertMarkedAsDeleted(INodesInPath iip, String snapshotName)
throws IOException {
final INodeDirectory dir = getSnapshottableRoot(iip);
final Snapshot.Root snapshotRoot = dir.getDirectorySnapshottableFeature()
.getSnapshotByName(dir, snapshotName)
.getRoot();
if (!snapshotRoot.isMarkedAsDeleted()) {
throw new SnapshotException("Failed to gcDeletedSnapshot "
+ snapshotName + " from " + dir.getFullPathName()
+ ": snapshot is not marked as deleted");
}
}
void assertPrior(INodeDirectory dir, String snapshotName, int prior)
throws SnapshotException {
if (!isSnapshotDeletionOrdered()) {
return;
}
// prior must not exist
if (prior != Snapshot.NO_SNAPSHOT_ID) {
throw new SnapshotException("Failed to removeSnapshot "
+ snapshotName + " from " + dir.getFullPathName()
+ ": Unexpected prior (=" + prior + ") when "
+ DFS_NAMENODE_SNAPSHOT_DELETION_ORDERED
+ " is " + isSnapshotDeletionOrdered());
}
}
void assertFirstSnapshot(INodeDirectory dir,
DirectorySnapshottableFeature snapshottable, Snapshot snapshot)
throws SnapshotException {
final INodeDirectoryAttributes first
= snapshottable.getDiffs().getFirstSnapshotINode();
if (snapshot.getRoot() != first) {
throw new SnapshotException("Failed to delete snapshot " + snapshot
+ " from " + dir.getFullPathName() + " since " + snapshot
+ " is not the first snapshot (=" + first + ") and "
+ DFS_NAMENODE_SNAPSHOT_DELETION_ORDERED
+ " is " + isSnapshotDeletionOrdered());
}
}
/**
* Return CaptureOpenFiles config value.
*/
boolean captureOpenFiles() {
return captureOpenFiles;
}
@VisibleForTesting
int getMaxSnapshotLimit() {
return maxSnapshotLimit;
}
/**
* Get the snapshot root directory for the given directory. The given
* directory must either be a snapshot root or a descendant of any
* snapshot root directories.
* @param iip INodesInPath for the directory to get snapshot root.
* @return the snapshot root INodeDirectory
*/
public INodeDirectory checkAndGetSnapshottableAncestorDir(
final INodesInPath iip) throws IOException {
final INodeDirectory dir = getSnapshottableAncestorDir(iip);
if (dir == null) {
throw new SnapshotException("Directory is neither snapshottable nor" +
" under a snap root!");
}
return dir;
}
public INodeDirectory getSnapshottableAncestorDir(final INodesInPath iip)
throws IOException {
final String path = iip.getPath();
final INode inode = iip.getLastINode();
final INodeDirectory dir;
if (inode instanceof INodeDirectory) {
dir = INodeDirectory.valueOf(inode, path);
} else {
dir = INodeDirectory.valueOf(iip.getINode(-2), iip.getParentPath());
}
if (dir.isSnapshottable()) {
return dir;
}
for (INodeDirectory snapRoot : this.snapshottables.values()) {
if (dir.isAncestorDirectory(snapRoot)) {
return snapRoot;
}
}
return null;
}
public boolean isDescendantOfSnapshotRoot(INodeDirectory dir) {
if (dir.isSnapshottable()) {
return true;
} else {
for (INodeDirectory p = dir; p != null; p = p.getParent()) {
if (this.snapshottables.containsValue(p)) {
return true;
}
}
return false;
}
}
/**
* Create a snapshot of the given path.
* It is assumed that the caller will perform synchronization.
*
* @param iip the INodes resolved from the snapshottable directory's path
* @param snapshotName
* The name of the snapshot.
* @param mtime is the snapshot creation time set by Time.now().
* @throws IOException
* Throw IOException when 1) the given path does not lead to an
* existing snapshottable directory, and/or 2) there exists a
* snapshot with the given name for the directory, and/or 3)
* snapshot number exceeds quota
*/
public String createSnapshot(final LeaseManager leaseManager,
final INodesInPath iip, String snapshotRoot, String snapshotName,
long mtime)
throws IOException {
INodeDirectory srcRoot = getSnapshottableRoot(iip);
if (snapshotCounter == getMaxSnapshotID()) {
// We have reached the maximum allowable snapshot ID and since we don't
// handle rollover we will fail all subsequent snapshot creation
// requests.
throw new SnapshotException(
"Failed to create the snapshot. The FileSystem has run out of " +
"snapshot IDs and ID rollover is not supported " +
"and the max snapshot limit is: " + maxSnapshotLimit);
}
int n = numSnapshots.get();
checkFileSystemSnapshotLimit(n);
srcRoot.addSnapshot(this, snapshotName, leaseManager, mtime);
//create success, update id
snapshotCounter++;
numSnapshots.getAndIncrement();
return Snapshot.getSnapshotPath(snapshotRoot, snapshotName);
}
void checkFileSystemSnapshotLimit(int n) throws SnapshotException {
checkSnapshotLimit(maxSnapshotFSLimit, n, "file system");
}
void checkPerDirectorySnapshotLimit(int n) throws SnapshotException {
checkSnapshotLimit(maxSnapshotLimit, n, "per directory");
}
void checkSnapshotLimit(int limit, int snapshotCount, String type)
throws SnapshotException {
if (snapshotCount >= limit) {
String msg = "there are already " + snapshotCount
+ " snapshot(s) and the " + type + " snapshot limit is "
+ limit;
if (isImageLoaded()) {
// We have reached the maximum snapshot limit
throw new SnapshotException(
"Failed to create snapshot: " + msg);
} else {
// image is getting loaded. LOG an error msg and continue
LOG.error(msg);
}
}
}
boolean isImageLoaded() {
return fsdir.isImageLoaded();
}
/**
* Delete a snapshot for a snapshottable directory
* @param snapshotName Name of the snapshot to be deleted
* @param now is the snapshot deletion time set by Time.now().
* @param reclaimContext Used to collect information to reclaim blocks
* and inodes
*/
public void deleteSnapshot(final INodesInPath iip, final String snapshotName,
INode.ReclaimContext reclaimContext, long now) throws IOException {
final INodeDirectory srcRoot = getSnapshottableRoot(iip);
if (isSnapshotDeletionOrdered()) {
final DirectorySnapshottableFeature snapshottable
= srcRoot.getDirectorySnapshottableFeature();
final Snapshot snapshot = snapshottable.getSnapshotByName(
srcRoot, snapshotName);
// Diffs must be not empty since a snapshot exists in the list
final int earliest = snapshottable.getDiffs().getFirst().getSnapshotId();
if (snapshot.getId() != earliest) {
final XAttr snapshotXAttr = buildXAttr();
final List<XAttr> xattrs = Lists.newArrayListWithCapacity(1);
xattrs.add(snapshotXAttr);
// The snapshot to be deleted is just marked for deletion in the xAttr.
// Same snaphot delete call can happen multiple times until and unless
// the very 1st instance of a snapshot delete hides it/remove it from
// snapshot list. XAttrSetFlag.REPLACE needs to be set to here in order
// to address this.
// XAttr will set on the snapshot root directory
// NOTE : This function is directly called while replaying the edit
// logs.While replaying the edit logs we need to mark the snapshot
// deleted in the xattr of the snapshot root.
FSDirXAttrOp.unprotectedSetXAttrs(fsdir,
INodesInPath.append(iip, snapshot.getRoot(),
DFSUtil.string2Bytes(snapshotName)), xattrs,
EnumSet.of(XAttrSetFlag.CREATE, XAttrSetFlag.REPLACE));
renameSnapshot(iip, srcRoot.getFullPathName(), snapshotName,
Snapshot.generateDeletedSnapshotName(snapshot), Time.now());
return;
}
assertFirstSnapshot(srcRoot, snapshottable, snapshot);
}
srcRoot.removeSnapshot(reclaimContext, snapshotName, now, this);
numSnapshots.getAndDecrement();
}
/**
* Rename the given snapshot
* @param oldSnapshotName
* Old name of the snapshot
* @param newSnapshotName
* New name of the snapshot
* @param now is the snapshot modification time set by Time.now().
* @throws IOException
* Throw IOException when 1) the given path does not lead to an
* existing snapshottable directory, and/or 2) the snapshot with the
* old name does not exist for the directory, and/or 3) there exists
* a snapshot with the new name for the directory
*/
public void renameSnapshot(final INodesInPath iip, final String snapshotRoot,
final String oldSnapshotName, final String newSnapshotName, long now)
throws IOException {
final INodeDirectory srcRoot = getSnapshottableRoot(iip);
srcRoot.renameSnapshot(snapshotRoot, oldSnapshotName, newSnapshotName, now);
}
public int getNumSnapshottableDirs() {
return snapshottables.size();
}
public int getNumSnapshots() {
return numSnapshots.get();
}
void setNumSnapshots(int num) {
numSnapshots.set(num);
}
int getSnapshotCounter() {
return snapshotCounter;
}
void setSnapshotCounter(int counter) {
snapshotCounter = counter;
}
List<INodeDirectory> getSnapshottableDirs() {
return new ArrayList<>(snapshottables.values());
}
/**
* Write {@link #snapshotCounter}, {@link #numSnapshots},
* and all snapshots to the DataOutput.
*/
public void write(DataOutput out) throws IOException {
out.writeInt(snapshotCounter);
out.writeInt(numSnapshots.get());
// write all snapshots.
for(INodeDirectory snapshottableDir : snapshottables.values()) {
for (Snapshot s : snapshottableDir.getDirectorySnapshottableFeature()
.getSnapshotList()) {
s.write(out);
}
}
}
/**
* Read values of {@link #snapshotCounter}, {@link #numSnapshots}, and
* all snapshots from the DataInput
*/
public Map<Integer, Snapshot> read(DataInput in, FSImageFormat.Loader loader
) throws IOException {
snapshotCounter = in.readInt();
numSnapshots.set(in.readInt());
// read snapshots
final Map<Integer, Snapshot> snapshotMap = new HashMap<Integer, Snapshot>();
for(int i = 0; i < numSnapshots.get(); i++) {
final Snapshot s = Snapshot.read(in, loader);
snapshotMap.put(s.getId(), s);
}
return snapshotMap;
}
/**
* List all the snapshottable directories that are owned by the current user.
* @param userName Current user name.
* @return Snapshottable directories that are owned by the current user,
* represented as an array of {@link SnapshottableDirectoryStatus}. If
* {@code userName} is null, return all the snapshottable dirs.
*/
public SnapshottableDirectoryStatus[] getSnapshottableDirListing(
String userName) {
if (snapshottables.isEmpty()) {
return null;
}
List<SnapshottableDirectoryStatus> statusList =
new ArrayList<SnapshottableDirectoryStatus>();
for (INodeDirectory dir : snapshottables.values()) {
if (userName == null || userName.equals(dir.getUserName())) {
SnapshottableDirectoryStatus status = new SnapshottableDirectoryStatus(
dir.getModificationTime(), dir.getAccessTime(),
dir.getFsPermission(), EnumSet.noneOf(HdfsFileStatus.Flags.class),
dir.getUserName(), dir.getGroupName(),
dir.getLocalNameBytes(), dir.getId(),
dir.getChildrenNum(Snapshot.CURRENT_STATE_ID),
dir.getDirectorySnapshottableFeature().getNumSnapshots(),
dir.getDirectorySnapshottableFeature().getSnapshotQuota(),
dir.getParent() == null ? DFSUtilClient.EMPTY_BYTES :
DFSUtil.string2Bytes(dir.getParent().getFullPathName()));
statusList.add(status);
}
}
Collections.sort(statusList, SnapshottableDirectoryStatus.COMPARATOR);
return statusList.toArray(
new SnapshottableDirectoryStatus[statusList.size()]);
}
/**
* List all the snapshots under a snapshottable directory.
*/
public SnapshotStatus[] getSnapshotListing(INodesInPath iip)
throws IOException {
INodeDirectory srcRoot = getSnapshottableRoot(iip);
ReadOnlyList<Snapshot> snapshotList = srcRoot.
getDirectorySnapshottableFeature().getSnapshotList();
SnapshotStatus[] statuses = new SnapshotStatus[snapshotList.size()];
for (int count = 0; count < snapshotList.size(); count++) {
Snapshot s = snapshotList.get(count);
Snapshot.Root dir = s.getRoot();
statuses[count] = new SnapshotStatus(dir.getModificationTime(),
dir.getAccessTime(), dir.getFsPermission(),
EnumSet.noneOf(HdfsFileStatus.Flags.class),
dir.getUserName(), dir.getGroupName(),
dir.getLocalNameBytes(), dir.getId(),
// the children number is same as the
// live fs as the children count is not cached per snashot.
// It is just used here to construct the HdfsFileStatus object.
// It is expensive to build the snapshot tree for the directory
// and determine the child count.
dir.getChildrenNum(Snapshot.CURRENT_STATE_ID),
s.getId(), s.getRoot().isMarkedAsDeleted(),
DFSUtil.string2Bytes(dir.getParent().getFullPathName()));
}
return statuses;
}
/**
* Compute the difference between two snapshots of a directory, or between a
* snapshot of the directory and its current tree.
*/
public SnapshotDiffReport diff(final INodesInPath iip,
final String snapshotPath, final String from,
final String to) throws IOException {
// Find the source root directory path where the snapshots were taken.
// All the check for path has been included in the valueOf method.
INodeDirectory snapshotRootDir;
if (this.snapshotDiffAllowSnapRootDescendant) {
snapshotRootDir = checkAndGetSnapshottableAncestorDir(iip);
} else {
snapshotRootDir = getSnapshottableRoot(iip);
}
Preconditions.checkNotNull(snapshotRootDir);
INodeDirectory snapshotDescendantDir = INodeDirectory.valueOf(
iip.getLastINode(), snapshotPath);
if ((from == null || from.isEmpty())
&& (to == null || to.isEmpty())) {
// both fromSnapshot and toSnapshot indicate the current tree
return new SnapshotDiffReport(snapshotPath, from, to,
Collections.<DiffReportEntry> emptyList());
}
final SnapshotDiffInfo diffs = snapshotRootDir
.getDirectorySnapshottableFeature().computeDiff(
snapshotRootDir, snapshotDescendantDir, from, to);
return diffs != null ? diffs.generateReport() : new SnapshotDiffReport(
snapshotPath, from, to, Collections.<DiffReportEntry> emptyList());
}
/**
* Compute the partial difference between two snapshots of a directory,
* or between a snapshot of the directory and its current tree.
*/
public SnapshotDiffReportListing diff(final INodesInPath iip,
final String snapshotPath, final String from, final String to,
byte[] startPath, int index, int snapshotDiffReportLimit)
throws IOException {
// Find the source root directory path where the snapshots were taken.
// All the check for path has been included in the valueOf method.
INodeDirectory snapshotRootDir;
if (this.snapshotDiffAllowSnapRootDescendant) {
snapshotRootDir = checkAndGetSnapshottableAncestorDir(iip);
} else {
snapshotRootDir = getSnapshottableRoot(iip);
}
Preconditions.checkNotNull(snapshotRootDir);
INodeDirectory snapshotDescendantDir = INodeDirectory.valueOf(
iip.getLastINode(), snapshotPath);
final SnapshotDiffListingInfo diffs =
snapshotRootDir.getDirectorySnapshottableFeature()
.computeDiff(snapshotRootDir, snapshotDescendantDir, from, to,
startPath, index, snapshotDiffReportLimit);
return diffs != null ? diffs.generateReport() :
new SnapshotDiffReportListing();
}
public void clearSnapshottableDirs() {
snapshottables.clear();
}
/**
* Returns the maximum allowable snapshot ID based on the bit width of the
* snapshot ID.
*
* @return maximum allowable snapshot ID.
*/
public int getMaxSnapshotID() {
return ((1 << SNAPSHOT_ID_BIT_WIDTH) - 1);
}
public static XAttr buildXAttr() {
return XAttrHelper.buildXAttr(HdfsServerConstants.XATTR_SNAPSHOT_DELETED);
}
private ObjectName mxBeanName;
public void registerMXBean() {
mxBeanName = MBeans.register("NameNode", "SnapshotInfo", this);
}
public void shutdown() {
MBeans.unregister(mxBeanName);
mxBeanName = null;
}
@Override // SnapshotStatsMXBean
public SnapshottableDirectoryStatus.Bean[]
getSnapshottableDirectories() {
List<SnapshottableDirectoryStatus.Bean> beans =
new ArrayList<SnapshottableDirectoryStatus.Bean>();
for (INodeDirectory d : getSnapshottableDirs()) {
beans.add(toBean(d));
}
return beans.toArray(new SnapshottableDirectoryStatus.Bean[beans.size()]);
}
@Override // SnapshotStatsMXBean
public SnapshotInfo.Bean[] getSnapshots() {
List<SnapshotInfo.Bean> beans = new ArrayList<SnapshotInfo.Bean>();
for (INodeDirectory d : getSnapshottableDirs()) {
for (Snapshot s : d.getDirectorySnapshottableFeature().getSnapshotList()) {
beans.add(toBean(s));
}
}
return beans.toArray(new SnapshotInfo.Bean[beans.size()]);
}
public static SnapshottableDirectoryStatus.Bean toBean(INodeDirectory d) {
return new SnapshottableDirectoryStatus.Bean(
d.getFullPathName(),
d.getDirectorySnapshottableFeature().getNumSnapshots(),
d.getDirectorySnapshottableFeature().getSnapshotQuota(),
d.getModificationTime(),
Short.parseShort(Integer.toOctalString(d.getFsPermissionShort())),
d.getUserName(),
d.getGroupName());
}
public static SnapshotInfo.Bean toBean(Snapshot s) {
Snapshot.Root dir = s.getRoot();
return new SnapshotInfo.Bean(
s.getId(),
dir.getFullPathName(),
dir.getModificationTime(),
dir.isMarkedAsDeleted()
);
}
private List<INodeDirectory> getSnapshottableDirsForGc() {
final List<INodeDirectory> dirs = getSnapshottableDirs();
Collections.shuffle(dirs);
return dirs;
}
Snapshot.Root chooseDeletedSnapshot() {
for(INodeDirectory dir : getSnapshottableDirsForGc()) {
final Snapshot.Root root = chooseDeletedSnapshot(dir);
if (root != null) {
return root;
}
}
return null;
}
private static Snapshot.Root chooseDeletedSnapshot(INodeDirectory dir) {
final DirectorySnapshottableFeature snapshottable
= dir.getDirectorySnapshottableFeature();
if (snapshottable == null) {
return null;
}
final DirectoryWithSnapshotFeature.DirectoryDiffList diffs
= snapshottable.getDiffs();
final Snapshot.Root first = (Snapshot.Root)diffs.getFirstSnapshotINode();
if (first == null || !first.isMarkedAsDeleted()) {
return null;
}
return first;
}
}