HDFS-4131. Add capability to namenode to get snapshot diff. Contributed by Jing Zhao.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/HDFS-2802@1440152 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
5988208b7d
commit
66ea187228
|
@ -130,3 +130,6 @@ Branch-2802 Snapshot (Unreleased)
|
||||||
|
|
||||||
HDFS-4432. Support INodeFileUnderConstructionWithSnapshot in FSImage
|
HDFS-4432. Support INodeFileUnderConstructionWithSnapshot in FSImage
|
||||||
saving/loading. (Jing Zhao via suresh)
|
saving/loading. (Jing Zhao via suresh)
|
||||||
|
|
||||||
|
HDFS-4131. Add capability to namenode to get snapshot diff. (Jing Zhao via
|
||||||
|
suresh)
|
||||||
|
|
|
@ -138,7 +138,6 @@ import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
|
||||||
import org.apache.hadoop.hdfs.protocol.DirectoryListing;
|
import org.apache.hadoop.hdfs.protocol.DirectoryListing;
|
||||||
import org.apache.hadoop.hdfs.protocol.ExtendedBlock;
|
import org.apache.hadoop.hdfs.protocol.ExtendedBlock;
|
||||||
import org.apache.hadoop.hdfs.protocol.HdfsConstants;
|
import org.apache.hadoop.hdfs.protocol.HdfsConstants;
|
||||||
import org.apache.hadoop.hdfs.protocol.SnapshottableDirectoryStatus;
|
|
||||||
import org.apache.hadoop.hdfs.protocol.HdfsConstants.DatanodeReportType;
|
import org.apache.hadoop.hdfs.protocol.HdfsConstants.DatanodeReportType;
|
||||||
import org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction;
|
import org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction;
|
||||||
import org.apache.hadoop.hdfs.protocol.HdfsFileStatus;
|
import org.apache.hadoop.hdfs.protocol.HdfsFileStatus;
|
||||||
|
@ -146,6 +145,7 @@ import org.apache.hadoop.hdfs.protocol.LocatedBlock;
|
||||||
import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
|
import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
|
||||||
import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
|
import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
|
||||||
import org.apache.hadoop.hdfs.protocol.RecoveryInProgressException;
|
import org.apache.hadoop.hdfs.protocol.RecoveryInProgressException;
|
||||||
|
import org.apache.hadoop.hdfs.protocol.SnapshottableDirectoryStatus;
|
||||||
import org.apache.hadoop.hdfs.protocol.datatransfer.ReplaceDatanodeOnFailure;
|
import org.apache.hadoop.hdfs.protocol.datatransfer.ReplaceDatanodeOnFailure;
|
||||||
import org.apache.hadoop.hdfs.security.token.block.BlockTokenSecretManager;
|
import org.apache.hadoop.hdfs.security.token.block.BlockTokenSecretManager;
|
||||||
import org.apache.hadoop.hdfs.security.token.block.BlockTokenSecretManager.AccessMode;
|
import org.apache.hadoop.hdfs.security.token.block.BlockTokenSecretManager.AccessMode;
|
||||||
|
@ -178,6 +178,7 @@ import org.apache.hadoop.hdfs.server.namenode.ha.StandbyCheckpointer;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.metrics.FSNamesystemMBean;
|
import org.apache.hadoop.hdfs.server.namenode.metrics.FSNamesystemMBean;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.metrics.NameNodeMetrics;
|
import org.apache.hadoop.hdfs.server.namenode.metrics.NameNodeMetrics;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable;
|
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable;
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable.SnapshotDiffReport;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeFileUnderConstructionWithSnapshot;
|
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeFileUnderConstructionWithSnapshot;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeFileWithSnapshot;
|
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeFileWithSnapshot;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
|
import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
|
||||||
|
@ -5814,6 +5815,37 @@ public class FSNamesystem implements Namesystem, FSClusterStats,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the difference between two snapshots (or between a snapshot and the
|
||||||
|
* current status) of a snapshottable directory.
|
||||||
|
*
|
||||||
|
* @param path The full path of the snapshottable directory.
|
||||||
|
* @param fromSnapshot Name of the snapshot to calculate the diff from. Null
|
||||||
|
* or empty string indicates the current tree.
|
||||||
|
* @param toSnapshot Name of the snapshot to calculated the diff to. Null or
|
||||||
|
* empty string indicates the current tree.
|
||||||
|
* @return The difference between {@code fromSnapshot} and {@code toSnapshot},
|
||||||
|
* i.e., applying difference to source will get target.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public SnapshotDiffReport getSnapshotDiffReport(String path,
|
||||||
|
String fromSnapshot, String toSnapshot) throws IOException {
|
||||||
|
SnapshotDiffReport diffs = null;
|
||||||
|
readLock();
|
||||||
|
try {
|
||||||
|
checkOperation(OperationCategory.READ);
|
||||||
|
diffs = snapshotManager.diff(path, fromSnapshot, toSnapshot);
|
||||||
|
} finally {
|
||||||
|
readUnlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
|
||||||
|
logAuditEvent(UserGroupInformation.getCurrentUser(), getRemoteIp(),
|
||||||
|
"computeSnapshotDiff", null, null, null);
|
||||||
|
}
|
||||||
|
return diffs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a snapshot of a snapshottable directory
|
* Delete a snapshot of a snapshottable directory
|
||||||
* @param snapshotRoot The snapshottable directory
|
* @param snapshotRoot The snapshottable directory
|
||||||
|
|
|
@ -622,6 +622,17 @@ public class INodeDirectory extends INode {
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare the metadata with another INodeDirectory
|
||||||
|
*/
|
||||||
|
public boolean metadataEquals(INodeDirectory other) {
|
||||||
|
return other != null && getNsQuota() == other.getNsQuota()
|
||||||
|
&& getDsQuota() == other.getDsQuota()
|
||||||
|
&& getUserName().equals(other.getUserName())
|
||||||
|
&& getGroupName().equals(other.getGroupName())
|
||||||
|
&& getFsPermission().equals(other.getFsPermission());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by
|
* Used by
|
||||||
* {@link INodeDirectory#getExistingPathINodes(byte[][], int, boolean)}.
|
* {@link INodeDirectory#getExistingPathINodes(byte[][], int, boolean)}.
|
||||||
|
|
|
@ -21,14 +21,19 @@ import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import org.apache.hadoop.HadoopIllegalArgumentException;
|
import org.apache.hadoop.HadoopIllegalArgumentException;
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.hdfs.DFSUtil;
|
import org.apache.hadoop.hdfs.DFSUtil;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.INode;
|
import org.apache.hadoop.hdfs.server.namenode.INode;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
|
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.snapshot.diff.Diff;
|
||||||
import org.apache.hadoop.hdfs.util.ReadOnlyList;
|
import org.apache.hadoop.hdfs.util.ReadOnlyList;
|
||||||
import org.apache.hadoop.util.Time;
|
import org.apache.hadoop.util.Time;
|
||||||
|
|
||||||
|
@ -55,6 +60,76 @@ public class INodeDirectorySnapshottable extends INodeDirectoryWithSnapshot {
|
||||||
}
|
}
|
||||||
return (INodeDirectorySnapshottable)dir;
|
return (INodeDirectorySnapshottable)dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class describing the difference between snapshots of a snapshottable
|
||||||
|
* directory.
|
||||||
|
*/
|
||||||
|
public static class SnapshotDiffReport {
|
||||||
|
public static final Comparator<INode> INODE_COMPARATOR =
|
||||||
|
new Comparator<INode>() {
|
||||||
|
@Override
|
||||||
|
public int compare(INode left, INode right) {
|
||||||
|
if (left == null) {
|
||||||
|
return right == null ? 0 : -1;
|
||||||
|
} else {
|
||||||
|
return right == null ? 1 : left.compareTo(right.getLocalNameBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The root directory of the snapshots */
|
||||||
|
private final INodeDirectorySnapshottable snapshotRoot;
|
||||||
|
/** The starting point of the difference */
|
||||||
|
private final Snapshot from;
|
||||||
|
/** The end point of the difference */
|
||||||
|
private final Snapshot to;
|
||||||
|
/**
|
||||||
|
* A map capturing the detailed difference. Each key indicates a directory
|
||||||
|
* whose metadata or children have been changed between the two snapshots,
|
||||||
|
* while its associated value is a {@link Diff} storing the changes happened
|
||||||
|
* to the children (files).
|
||||||
|
*/
|
||||||
|
private final SortedMap<INodeDirectoryWithSnapshot, ChildrenDiff> diffMap;
|
||||||
|
|
||||||
|
public SnapshotDiffReport(INodeDirectorySnapshottable snapshotRoot,
|
||||||
|
Snapshot start, Snapshot end) {
|
||||||
|
this.snapshotRoot = snapshotRoot;
|
||||||
|
this.from = start;
|
||||||
|
this.to = end;
|
||||||
|
this.diffMap = new TreeMap<INodeDirectoryWithSnapshot, ChildrenDiff>(
|
||||||
|
INODE_COMPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add a dir-diff pair into {@link #diffMap} */
|
||||||
|
public void addDiff(INodeDirectoryWithSnapshot dir, ChildrenDiff diff) {
|
||||||
|
diffMap.put(dir, diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dump the diff
|
||||||
|
*/
|
||||||
|
public String dump() {
|
||||||
|
StringBuilder strBuffer = new StringBuilder();
|
||||||
|
String fromStr = from == null ? "current directory" : "snapshot "
|
||||||
|
+ from.getRoot().getLocalName();
|
||||||
|
String toStr = to == null ? "current directory" : "snapshot "
|
||||||
|
+ to.getRoot().getLocalName();
|
||||||
|
strBuffer.append("Diffence between snapshot " + fromStr + " and " + toStr
|
||||||
|
+ " under directory " + snapshotRoot.getFullPathName() + ":\n");
|
||||||
|
|
||||||
|
if (!diffMap.isEmpty()) {
|
||||||
|
for (Map.Entry<INodeDirectoryWithSnapshot, ChildrenDiff> entry : diffMap
|
||||||
|
.entrySet()) {
|
||||||
|
strBuffer.append("M\t" + entry.getKey().getFullPathName() + "\n");
|
||||||
|
entry.getValue().printDiff(strBuffer, entry.getKey(),
|
||||||
|
from == null ||
|
||||||
|
(to != null && Snapshot.ID_COMPARATOR.compare(from, to) > 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strBuffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Snapshots of this directory in ascending order of snapshot names.
|
* Snapshots of this directory in ascending order of snapshot names.
|
||||||
|
@ -229,6 +304,80 @@ public class INodeDirectorySnapshottable extends INodeDirectoryWithSnapshot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the difference between two snapshots (or a snapshot and the current
|
||||||
|
* directory) of the directory.
|
||||||
|
*
|
||||||
|
* @param from The name of the start point of the comparison. Null indicating
|
||||||
|
* the current tree.
|
||||||
|
* @param to The name of the end point. Null indicating the current tree.
|
||||||
|
* @return The difference between the start/end points.
|
||||||
|
* @throws SnapshotException If there is no snapshot matching the starting
|
||||||
|
* point, or if endSnapshotName is not null but cannot be identified
|
||||||
|
* as a previous snapshot.
|
||||||
|
*/
|
||||||
|
SnapshotDiffReport computeDiff(final String from, final String to)
|
||||||
|
throws SnapshotException {
|
||||||
|
Snapshot fromSnapshot = getSnapshotByName(from);
|
||||||
|
Snapshot toSnapshot = getSnapshotByName(to);
|
||||||
|
SnapshotDiffReport diffs = new SnapshotDiffReport(this, fromSnapshot,
|
||||||
|
toSnapshot);
|
||||||
|
computeDiffInDir(this, diffs);
|
||||||
|
return diffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the snapshot matching the given name.
|
||||||
|
*
|
||||||
|
* @param snapshotName The name of the snapshot.
|
||||||
|
* @return The corresponding snapshot. Null if snapshotName is null or empty.
|
||||||
|
* @throws SnapshotException If snapshotName is not null or empty, but there
|
||||||
|
* is no snapshot matching the name.
|
||||||
|
*/
|
||||||
|
private Snapshot getSnapshotByName(String snapshotName)
|
||||||
|
throws SnapshotException {
|
||||||
|
Snapshot s = null;
|
||||||
|
if (snapshotName != null && !snapshotName.isEmpty()) {
|
||||||
|
final int index = searchSnapshot(DFSUtil.string2Bytes(snapshotName));
|
||||||
|
if (index < 0) {
|
||||||
|
throw new SnapshotException("Cannot find the snapshot of directory "
|
||||||
|
+ this.getFullPathName() + " with name " + snapshotName);
|
||||||
|
}
|
||||||
|
s = snapshotsByNames.get(index);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively compute the difference between snapshots under a given
|
||||||
|
* directory.
|
||||||
|
* @param dir The directory under which the diff is computed.
|
||||||
|
* @param diffReport data structure used to store the diff.
|
||||||
|
*/
|
||||||
|
private void computeDiffInDir(INodeDirectory dir,
|
||||||
|
SnapshotDiffReport diffReport) {
|
||||||
|
ChildrenDiff diff = new ChildrenDiff();
|
||||||
|
if (dir instanceof INodeDirectoryWithSnapshot) {
|
||||||
|
boolean change = ((INodeDirectoryWithSnapshot) dir)
|
||||||
|
.computeDiffBetweenSnapshots(diffReport.from,
|
||||||
|
diffReport.to, diff);
|
||||||
|
if (change) {
|
||||||
|
diffReport.addDiff((INodeDirectoryWithSnapshot) dir,
|
||||||
|
diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReadOnlyList<INode> children = dir.getChildrenList(null);
|
||||||
|
for (INode child : children) {
|
||||||
|
if (child instanceof INodeDirectory
|
||||||
|
&& diff.searchCreated(child.getLocalNameBytes()) == null) {
|
||||||
|
// Compute diff recursively for children that are directories. We do not
|
||||||
|
// need to compute diff for those contained in the created list since
|
||||||
|
// directory contained in the created list must be new created.
|
||||||
|
computeDiffInDir((INodeDirectory) child, diffReport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace itself with {@link INodeDirectoryWithSnapshot} or
|
* Replace itself with {@link INodeDirectoryWithSnapshot} or
|
||||||
* {@link INodeDirectory} depending on the latest snapshot.
|
* {@link INodeDirectory} depending on the latest snapshot.
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
import org.apache.hadoop.fs.permission.FsPermission;
|
import org.apache.hadoop.fs.permission.FsPermission;
|
||||||
import org.apache.hadoop.hdfs.DFSUtil;
|
import org.apache.hadoop.hdfs.DFSUtil;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization;
|
import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization;
|
||||||
|
@ -50,7 +51,7 @@ public class INodeDirectoryWithSnapshot extends INodeDirectoryWithQuota {
|
||||||
* of the children list of an INodeDirectory.
|
* of the children list of an INodeDirectory.
|
||||||
*/
|
*/
|
||||||
public static class ChildrenDiff extends Diff<byte[], INode> {
|
public static class ChildrenDiff extends Diff<byte[], INode> {
|
||||||
private ChildrenDiff() {}
|
ChildrenDiff() {}
|
||||||
|
|
||||||
private ChildrenDiff(final List<INode> created, final List<INode> deleted) {
|
private ChildrenDiff(final List<INode> created, final List<INode> deleted) {
|
||||||
super(created, deleted);
|
super(created, deleted);
|
||||||
|
@ -116,6 +117,59 @@ public class INodeDirectoryWithSnapshot extends INodeDirectoryWithQuota {
|
||||||
}
|
}
|
||||||
return dirList;
|
return dirList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print out the content of the Diff. In general, "M"/"+"/"-" are used to
|
||||||
|
* represent files/directories that were modified, created, and deleted,
|
||||||
|
* respectively.
|
||||||
|
*
|
||||||
|
* @param str A StringBuilder used to storing the description of the Diff.
|
||||||
|
* @param parent The directory that the Diff is about. Used to get the full
|
||||||
|
* path of the INodes contained in the Diff.
|
||||||
|
*/
|
||||||
|
public void printDiff(StringBuilder str, INodeDirectoryWithSnapshot parent,
|
||||||
|
boolean reverse) {
|
||||||
|
final String mStr = "M\t";
|
||||||
|
final String cStr = reverse ? "-\t" : "+\t";
|
||||||
|
final String dStr = reverse ? "+\t" : "-\t";
|
||||||
|
StringBuilder cBuffer = new StringBuilder();
|
||||||
|
StringBuilder dBuffer = new StringBuilder();
|
||||||
|
StringBuilder mBuffer = new StringBuilder();
|
||||||
|
int c = 0;
|
||||||
|
int d = 0;
|
||||||
|
List<INode> created = getCreatedList();
|
||||||
|
List<INode> deleted = getDeletedList();
|
||||||
|
for (; c < created.size() && d < deleted.size(); ) {
|
||||||
|
INode cnode = created.get(c);
|
||||||
|
INode dnode = deleted.get(d);
|
||||||
|
if (cnode.equals(dnode)) {
|
||||||
|
mBuffer.append(mStr + parent.getFullPathName() + Path.SEPARATOR
|
||||||
|
+ cnode.getLocalName() + "\n");
|
||||||
|
c++;
|
||||||
|
d++;
|
||||||
|
} else if (cnode.compareTo(dnode.getLocalNameBytes()) < 0) {
|
||||||
|
cBuffer.append(cStr + parent.getFullPathName() + Path.SEPARATOR
|
||||||
|
+ cnode.getLocalName() + "\n");
|
||||||
|
c++;
|
||||||
|
} else {
|
||||||
|
dBuffer.append(dStr + parent.getFullPathName() + Path.SEPARATOR
|
||||||
|
+ dnode.getLocalName() + "\n");
|
||||||
|
d++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; d < deleted.size(); d++) {
|
||||||
|
dBuffer.append(dStr + parent.getFullPathName() + Path.SEPARATOR
|
||||||
|
+ deleted.get(d).getLocalName() + "\n");
|
||||||
|
}
|
||||||
|
for (; c < created.size(); c++) {
|
||||||
|
cBuffer.append(cStr + parent.getFullPathName() + Path.SEPARATOR
|
||||||
|
+ created.get(c).getLocalName() + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
str.append(cBuffer);
|
||||||
|
str.append(dBuffer);
|
||||||
|
str.append(mBuffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -324,6 +378,79 @@ public class INodeDirectoryWithSnapshot extends INodeDirectoryWithQuota {
|
||||||
}
|
}
|
||||||
return withSnapshot;
|
return withSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the difference between Snapshots.
|
||||||
|
*
|
||||||
|
* @param fromSnapshot Start point of the diff computation. Null indicates
|
||||||
|
* current tree.
|
||||||
|
* @param toSnapshot End point of the diff computation. Null indicates current
|
||||||
|
* tree.
|
||||||
|
* @param diff Used to capture the changes happening to the children. Note
|
||||||
|
* that the diff still represents (later_snapshot - earlier_snapshot)
|
||||||
|
* although toSnapshot can be before fromSnapshot.
|
||||||
|
* @return Whether changes happened between the startSnapshot and endSnaphsot.
|
||||||
|
*/
|
||||||
|
boolean computeDiffBetweenSnapshots(Snapshot fromSnapshot,
|
||||||
|
Snapshot toSnapshot, ChildrenDiff diff) {
|
||||||
|
Snapshot earlierSnapshot = fromSnapshot;
|
||||||
|
Snapshot laterSnapshot = toSnapshot;
|
||||||
|
if (fromSnapshot == null
|
||||||
|
|| (toSnapshot != null && Snapshot.ID_COMPARATOR.compare(fromSnapshot,
|
||||||
|
toSnapshot) > 0)) {
|
||||||
|
earlierSnapshot = toSnapshot;
|
||||||
|
laterSnapshot = fromSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
int earlierDiffIndex = Collections.binarySearch(diffs, earlierSnapshot);
|
||||||
|
if (earlierDiffIndex < 0 && (-earlierDiffIndex - 1) == diffs.size()) {
|
||||||
|
// if the earlierSnapshot is after the latest SnapshotDiff stored in diffs,
|
||||||
|
// no modification happened after the earlierSnapshot
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int laterDiffIndex = diffs.size();
|
||||||
|
if (laterSnapshot != null) {
|
||||||
|
laterDiffIndex = Collections.binarySearch(diffs, laterSnapshot);
|
||||||
|
if (laterDiffIndex == -1 || laterDiffIndex == 0) {
|
||||||
|
// if the endSnapshot is the earliest SnapshotDiff stored in
|
||||||
|
// diffs, or before it, no modification happened before the endSnapshot
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
earlierDiffIndex = earlierDiffIndex < 0 ? (-earlierDiffIndex - 1)
|
||||||
|
: earlierDiffIndex;
|
||||||
|
laterDiffIndex = laterDiffIndex < 0 ? (-laterDiffIndex - 1)
|
||||||
|
: laterDiffIndex;
|
||||||
|
boolean dirMetadataChanged = false;
|
||||||
|
INodeDirectory dirCopy = null;
|
||||||
|
for (int i = earlierDiffIndex; i < laterDiffIndex; i++) {
|
||||||
|
SnapshotDiff sdiff = diffs.get(i);
|
||||||
|
diff.combinePosterior(sdiff.diff, null);
|
||||||
|
if (dirMetadataChanged == false && sdiff.snapshotINode != null) {
|
||||||
|
if (dirCopy == null) {
|
||||||
|
dirCopy = sdiff.snapshotINode;
|
||||||
|
} else {
|
||||||
|
if (!dirCopy.metadataEquals(sdiff.snapshotINode)) {
|
||||||
|
dirMetadataChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!diff.isEmpty() || dirMetadataChanged) {
|
||||||
|
return true;
|
||||||
|
} else if (dirCopy != null) {
|
||||||
|
for (int i = laterDiffIndex; i < diffs.size(); i++) {
|
||||||
|
if (diffs.get(i).snapshotINode != null
|
||||||
|
&& !dirCopy.metadataEquals(diffs.get(i).snapshotINode)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !dirCopy.metadataEquals(this);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/** Diff list sorted by snapshot IDs, i.e. in chronological order. */
|
/** Diff list sorted by snapshot IDs, i.e. in chronological order. */
|
||||||
private final List<SnapshotDiff> diffs;
|
private final List<SnapshotDiff> diffs;
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo;
|
import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
|
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
|
||||||
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory.INodesInPath;
|
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory.INodesInPath;
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable.SnapshotDiffReport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage snapshottable directories and their snapshots.
|
* Manage snapshottable directories and their snapshots.
|
||||||
|
@ -233,4 +234,30 @@ public class SnapshotManager implements SnapshotStats {
|
||||||
this.snapshottables.removeAll(toRemoveList);
|
this.snapshottables.removeAll(toRemoveList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the difference between two snapshots of a directory, or between a
|
||||||
|
* snapshot of the directory and its current tree.
|
||||||
|
*/
|
||||||
|
public SnapshotDiffReport diff(final String path, final String from,
|
||||||
|
final String to) throws IOException {
|
||||||
|
if ((from == null || from.isEmpty())
|
||||||
|
&& (to == null || to.isEmpty())) {
|
||||||
|
// both fromSnapshot and toSnapshot indicate the current tree
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// if the start point is equal to the end point, return null
|
||||||
|
if (from.equals(to)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the source root directory path where the snapshots were taken.
|
||||||
|
// All the check for path has been included in the valueOf method.
|
||||||
|
INodesInPath inodesInPath = fsdir.getMutableINodesInPath(path.toString());
|
||||||
|
final INodeDirectorySnapshottable snapshotRoot = INodeDirectorySnapshottable
|
||||||
|
.valueOf(inodesInPath.getLastINode(), path);
|
||||||
|
|
||||||
|
return snapshotRoot.computeDiff(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -180,6 +180,12 @@ public class Diff<K, E extends Diff.Element<K>> {
|
||||||
return d < 0 ? null : deleted.get(d);
|
return d < 0 ? null : deleted.get(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return true if no changes contained in the diff */
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return (created == null || created.isEmpty())
|
||||||
|
&& (deleted == null || deleted.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert the element to created.
|
* Insert the element to created.
|
||||||
* @param i the insertion point defined
|
* @param i the insertion point defined
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
/**
|
||||||
|
* 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.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.hdfs.DFSTestUtil;
|
||||||
|
import org.apache.hadoop.hdfs.DistributedFileSystem;
|
||||||
|
import org.apache.hadoop.hdfs.MiniDFSCluster;
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
|
||||||
|
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable.SnapshotDiffReport;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests snapshot deletion.
|
||||||
|
*/
|
||||||
|
public class TestSnapshotDiffReport {
|
||||||
|
protected static final long seed = 0;
|
||||||
|
protected static final short REPLICATION = 3;
|
||||||
|
protected static final long BLOCKSIZE = 1024;
|
||||||
|
public static final int SNAPSHOTNUMBER = 10;
|
||||||
|
|
||||||
|
private final Path dir = new Path("/TestSnapshot");
|
||||||
|
private final Path sub1 = new Path(dir, "sub1");
|
||||||
|
|
||||||
|
protected Configuration conf;
|
||||||
|
protected MiniDFSCluster cluster;
|
||||||
|
protected FSNamesystem fsn;
|
||||||
|
protected DistributedFileSystem hdfs;
|
||||||
|
|
||||||
|
private int snapshotNum = 0;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
conf = new Configuration();
|
||||||
|
cluster = new MiniDFSCluster.Builder(conf).numDataNodes(REPLICATION)
|
||||||
|
.format(true).build();
|
||||||
|
cluster.waitActive();
|
||||||
|
|
||||||
|
fsn = cluster.getNamesystem();
|
||||||
|
hdfs = cluster.getFileSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
if (cluster != null) {
|
||||||
|
cluster.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create/modify/delete files and create snapshots under a given directory.
|
||||||
|
*/
|
||||||
|
private void modifyAndCreateSnapshot(Path modifyDir, Path snapshotDir)
|
||||||
|
throws Exception {
|
||||||
|
Path file10 = new Path(modifyDir, "file10");
|
||||||
|
Path file11 = new Path(modifyDir, "file11");
|
||||||
|
Path file12 = new Path(modifyDir, "file12");
|
||||||
|
Path file13 = new Path(modifyDir, "file13");
|
||||||
|
Path file14 = new Path(modifyDir, "file14");
|
||||||
|
Path file15 = new Path(modifyDir, "file15");
|
||||||
|
DFSTestUtil.createFile(hdfs, file10, BLOCKSIZE, (short) (REPLICATION - 1),
|
||||||
|
seed);
|
||||||
|
DFSTestUtil.createFile(hdfs, file11, BLOCKSIZE, (short) (REPLICATION - 1),
|
||||||
|
seed);
|
||||||
|
DFSTestUtil.createFile(hdfs, file12, BLOCKSIZE, (short) (REPLICATION - 1),
|
||||||
|
seed);
|
||||||
|
DFSTestUtil.createFile(hdfs, file13, BLOCKSIZE, (short) (REPLICATION - 1),
|
||||||
|
seed);
|
||||||
|
// create snapshot s1
|
||||||
|
hdfs.allowSnapshot(snapshotDir.toString());
|
||||||
|
hdfs.createSnapshot(snapshotDir, "s" + snapshotNum++);
|
||||||
|
|
||||||
|
// delete file11
|
||||||
|
hdfs.delete(file11, true);
|
||||||
|
// modify file12
|
||||||
|
hdfs.setReplication(file12, REPLICATION);
|
||||||
|
// modify file13
|
||||||
|
hdfs.setReplication(file13, REPLICATION);
|
||||||
|
// create file14
|
||||||
|
DFSTestUtil.createFile(hdfs, file14, BLOCKSIZE, REPLICATION, seed);
|
||||||
|
// create file15
|
||||||
|
DFSTestUtil.createFile(hdfs, file15, BLOCKSIZE, REPLICATION, seed);
|
||||||
|
|
||||||
|
// create snapshot s2
|
||||||
|
hdfs.createSnapshot(snapshotDir, "s" + snapshotNum++);
|
||||||
|
|
||||||
|
// create file11 again
|
||||||
|
DFSTestUtil.createFile(hdfs, file11, BLOCKSIZE, REPLICATION, seed);
|
||||||
|
// delete file12
|
||||||
|
hdfs.delete(file12, true);
|
||||||
|
// modify file13
|
||||||
|
hdfs.setReplication(file13, (short) (REPLICATION - 2));
|
||||||
|
// delete file14
|
||||||
|
hdfs.delete(file14, true);
|
||||||
|
// modify file15
|
||||||
|
hdfs.setReplication(file15, (short) (REPLICATION - 1));
|
||||||
|
|
||||||
|
// create snapshot s3 for dir
|
||||||
|
hdfs.createSnapshot(snapshotDir, "s" + snapshotNum++);
|
||||||
|
// modify file10
|
||||||
|
hdfs.setReplication(file10, (short) (REPLICATION - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the functionality of
|
||||||
|
* {@link FSNamesystem#getSnapshotDiffReport(String, String, String)}.
|
||||||
|
* TODO: without the definision of a DiffReport class, this test temporarily
|
||||||
|
* check the output string of {@link SnapshotDiffReport#dump()}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDiff() throws Exception {
|
||||||
|
modifyAndCreateSnapshot(sub1, sub1);
|
||||||
|
modifyAndCreateSnapshot(new Path(sub1, "subsub1/subsubsub1"), sub1);
|
||||||
|
|
||||||
|
SnapshotDiffReport diffs = fsn.getSnapshotDiffReport(sub1.toString(), "s0", "s2");
|
||||||
|
String diffStr = diffs.dump();
|
||||||
|
System.out.println(diffStr);
|
||||||
|
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1"));
|
||||||
|
assertTrue(diffStr.contains("+\t/TestSnapshot/sub1/file15"));
|
||||||
|
assertTrue(diffStr.contains("-\t/TestSnapshot/sub1/file12"));
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1/file11"));
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1/file13"));
|
||||||
|
assertFalse(diffStr.contains("file14"));
|
||||||
|
|
||||||
|
diffs = fsn.getSnapshotDiffReport(sub1.toString(), "s0", "s5");
|
||||||
|
diffStr = diffs.dump();
|
||||||
|
System.out.println(diffStr);
|
||||||
|
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1"));
|
||||||
|
assertTrue(diffStr.contains("+\t/TestSnapshot/sub1/file15"));
|
||||||
|
assertTrue(diffStr.contains("+\t/TestSnapshot/sub1/subsub1"));
|
||||||
|
assertTrue(diffStr.contains("-\t/TestSnapshot/sub1/file12"));
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1/file10"));
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1/file11"));
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1/file13"));
|
||||||
|
assertFalse(diffStr.contains("file14"));
|
||||||
|
|
||||||
|
diffs = fsn.getSnapshotDiffReport(sub1.toString(), "s0", "");
|
||||||
|
diffStr = diffs.dump();
|
||||||
|
System.out.println(diffStr);
|
||||||
|
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1"));
|
||||||
|
assertTrue(diffStr.contains("+\t/TestSnapshot/sub1/file15"));
|
||||||
|
assertTrue(diffStr.contains("+\t/TestSnapshot/sub1/subsub1"));
|
||||||
|
assertTrue(diffStr.contains("-\t/TestSnapshot/sub1/file12"));
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1/file10"));
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1/file11"));
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1/file13"));
|
||||||
|
assertFalse(diffStr.contains("file14"));
|
||||||
|
|
||||||
|
diffs = fsn.getSnapshotDiffReport(sub1.toString(), "s2", "s5");
|
||||||
|
diffStr = diffs.dump();
|
||||||
|
System.out.println(diffStr);
|
||||||
|
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1"));
|
||||||
|
assertTrue(diffStr.contains("+\t/TestSnapshot/sub1/subsub1"));
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1/file10"));
|
||||||
|
|
||||||
|
diffs = fsn.getSnapshotDiffReport(sub1.toString(), "s3", "");
|
||||||
|
diffStr = diffs.dump();
|
||||||
|
System.out.println(diffStr);
|
||||||
|
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1"));
|
||||||
|
assertTrue(diffStr.contains("M\t/TestSnapshot/sub1/subsub1/subsubsub1"));
|
||||||
|
assertTrue(diffStr
|
||||||
|
.contains("+\t/TestSnapshot/sub1/subsub1/subsubsub1/file15"));
|
||||||
|
assertTrue(diffStr
|
||||||
|
.contains("-\t/TestSnapshot/sub1/subsub1/subsubsub1/file12"));
|
||||||
|
assertTrue(diffStr
|
||||||
|
.contains("M\t/TestSnapshot/sub1/subsub1/subsubsub1/file10"));
|
||||||
|
assertTrue(diffStr
|
||||||
|
.contains("M\t/TestSnapshot/sub1/subsub1/subsubsub1/file11"));
|
||||||
|
assertTrue(diffStr
|
||||||
|
.contains("M\t/TestSnapshot/sub1/subsub1/subsubsub1/file13"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue