blob: 58dd2cf0a3f04795107b8e6e34cd18b648707707 [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 java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffReportEntry;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffType;
import org.apache.hadoop.hdfs.server.namenode.INode;
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
import org.apache.hadoop.hdfs.server.namenode.INodeFile;
import org.apache.hadoop.hdfs.server.namenode.INodeReference;
import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.ChildrenDiff;
import org.apache.hadoop.thirdparty.com.google.common.base.Preconditions;
import org.apache.hadoop.thirdparty.com.google.common.primitives.SignedBytes;
import org.apache.hadoop.util.ChunkedArrayList;
/**
* A class describing the difference between snapshots of a snapshottable
* directory.
*/
class SnapshotDiffInfo {
/** Compare two inodes based on their full names */
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 {
if (right == null) {
return 1;
} else {
int cmp = compare(left.getParent(), right.getParent());
return cmp == 0 ? SignedBytes.lexicographicalComparator().compare(
left.getLocalNameBytes(), right.getLocalNameBytes()) : cmp;
}
}
}
};
static class RenameEntry {
private byte[][] sourcePath;
private byte[][] targetPath;
void setSource(INode source, byte[][] sourceParentPath) {
Preconditions.checkState(sourcePath == null);
sourcePath = new byte[sourceParentPath.length + 1][];
System.arraycopy(sourceParentPath, 0, sourcePath, 0,
sourceParentPath.length);
sourcePath[sourcePath.length - 1] = source.getLocalNameBytes();
}
void setTarget(INode target, byte[][] targetParentPath) {
targetPath = new byte[targetParentPath.length + 1][];
System.arraycopy(targetParentPath, 0, targetPath, 0,
targetParentPath.length);
targetPath[targetPath.length - 1] = target.getLocalNameBytes();
}
void setTarget(byte[][] targetPath) {
this.targetPath = targetPath;
}
boolean isRename() {
return sourcePath != null && targetPath != null;
}
byte[][] getSourcePath() {
return sourcePath;
}
byte[][] getTargetPath() {
return targetPath;
}
}
/** The root directory of the snapshots */
private final INodeDirectory snapshotRoot;
/**
* The scope directory under which snapshot diff is calculated.
*/
private final INodeDirectory snapshotDiffScopeDir;
/** The starting point of the difference */
private final Snapshot from;
/** The end point of the difference */
private final Snapshot to;
/**
* A map recording modified INodeFile and INodeDirectory and their relative
* path corresponding to the snapshot root. Sorted based on their names.
*/
private final SortedMap<INode, byte[][]> diffMap =
new TreeMap<INode, byte[][]>(INODE_COMPARATOR);
/**
* A map capturing the detailed difference about file creation/deletion.
* Each key indicates a directory whose children have been changed between
* the two snapshots, while its associated value is a {@link ChildrenDiff}
* storing the changes (creation/deletion) happened to the children (files).
*/
private final Map<INodeDirectory, ChildrenDiff> dirDiffMap =
new HashMap<INodeDirectory, ChildrenDiff>();
private final Map<Long, RenameEntry> renameMap =
new HashMap<Long, RenameEntry>();
// Total directories compared
private long totalDirsCompared;
// Total directories
private long totalDirsProcessed;
// Total files compared
private long totalFilesCompared;
// Total files
private long totalFilesProcessed;
// Total children listing time
private long childrenListingTime;
SnapshotDiffInfo(INodeDirectory snapshotRootDir,
INodeDirectory snapshotDiffScopeDir, Snapshot start, Snapshot end) {
Preconditions.checkArgument(snapshotRootDir.isSnapshottable() &&
snapshotDiffScopeDir.isDescendantOfSnapshotRoot(snapshotRootDir));
this.snapshotRoot = snapshotRootDir;
this.snapshotDiffScopeDir = snapshotDiffScopeDir;
this.from = start;
this.to = end;
this.totalDirsCompared = 0;
this.totalDirsProcessed = 0;
this.totalFilesCompared = 0;
this.totalFilesProcessed = 0;
}
/** Add a dir-diff pair */
void addDirDiff(INodeDirectory dir, byte[][] relativePath, ChildrenDiff diff) {
dirDiffMap.put(dir, diff);
diffMap.put(dir, relativePath);
// detect rename
for (INode created : diff.getCreatedUnmodifiable()) {
if (created.isReference()) {
RenameEntry entry = getEntry(created.getId());
if (entry.getTargetPath() == null) {
entry.setTarget(created, relativePath);
}
}
}
for (INode deleted : diff.getDeletedUnmodifiable()) {
if (deleted instanceof INodeReference.WithName) {
RenameEntry entry = getEntry(deleted.getId());
entry.setSource(deleted, relativePath);
}
}
}
Snapshot getFrom() {
return from;
}
Snapshot getTo() {
return to;
}
void incrementDirsCompared() {
this.totalDirsCompared++;
incrementDirsProcessed();
}
void incrementDirsProcessed() {
this.totalDirsProcessed++;
}
void incrementFilesCompared() {
this.totalFilesCompared++;
incrementFilesProcessed();
}
void incrementFilesProcessed() {
this.totalFilesProcessed++;
}
public void addChildrenListingTime(long millis) {
this.childrenListingTime += millis;
}
private RenameEntry getEntry(long inodeId) {
RenameEntry entry = renameMap.get(inodeId);
if (entry == null) {
entry = new RenameEntry();
renameMap.put(inodeId, entry);
}
return entry;
}
void setRenameTarget(long inodeId, byte[][] path) {
getEntry(inodeId).setTarget(path);
}
/** Add a modified file */
void addFileDiff(INodeFile file, byte[][] relativePath) {
diffMap.put(file, relativePath);
}
/** @return True if {@link #from} is earlier than {@link #to} */
boolean isFromEarlier() {
return Snapshot.ID_COMPARATOR.compare(from, to) < 0;
}
/**
* Generate a {@link SnapshotDiffReport} based on detailed diff information.
* @return A {@link SnapshotDiffReport} describing the difference
*/
public SnapshotDiffReport generateReport() {
List<DiffReportEntry> diffReportList = new ChunkedArrayList<>();
for (Map.Entry<INode,byte[][]> drEntry : diffMap.entrySet()) {
INode node = drEntry.getKey();
byte[][] path = drEntry.getValue();
diffReportList.add(new DiffReportEntry(DiffType.MODIFY, path, null));
if (node.isDirectory()) {
List<DiffReportEntry> subList = generateReport(dirDiffMap.get(node),
path, isFromEarlier(), renameMap);
diffReportList.addAll(subList);
}
}
SnapshotDiffReport.DiffStats dStats = new SnapshotDiffReport.DiffStats(
this.totalDirsCompared, this.totalDirsProcessed,
this.totalFilesCompared, this.totalFilesProcessed,
this.childrenListingTime);
return new SnapshotDiffReport(snapshotRoot.getFullPathName(),
Snapshot.getSnapshotName(from), Snapshot.getSnapshotName(to),
dStats, diffReportList);
}
/**
* Interpret the ChildrenDiff and generate a list of {@link DiffReportEntry}.
* @param dirDiff The ChildrenDiff.
* @param parentPath The relative path of the parent.
* @param fromEarlier True indicates {@code diff=later-earlier},
* False indicates {@code diff=earlier-later}
* @param renameMap A map containing information about rename operations.
* @return A list of {@link DiffReportEntry} as the diff report.
*/
private List<DiffReportEntry> generateReport(ChildrenDiff dirDiff,
byte[][] parentPath, boolean fromEarlier, Map<Long, RenameEntry> renameMap) {
List<DiffReportEntry> list = new ChunkedArrayList<>();
byte[][] fullPath = new byte[parentPath.length + 1][];
System.arraycopy(parentPath, 0, fullPath, 0, parentPath.length);
for (INode cnode : dirDiff.getCreatedUnmodifiable()) {
RenameEntry entry = renameMap.get(cnode.getId());
if (entry == null || !entry.isRename()) {
fullPath[fullPath.length - 1] = cnode.getLocalNameBytes();
list.add(new DiffReportEntry(fromEarlier ? DiffType.CREATE
: DiffType.DELETE, fullPath));
}
}
for (INode dnode : dirDiff.getDeletedUnmodifiable()) {
RenameEntry entry = renameMap.get(dnode.getId());
if (entry != null && entry.isRename()) {
list.add(new DiffReportEntry(DiffType.RENAME,
fromEarlier ? entry.getSourcePath() : entry.getTargetPath(),
fromEarlier ? entry.getTargetPath() : entry.getSourcePath()));
} else {
fullPath[fullPath.length - 1] = dnode.getLocalNameBytes();
list.add(new DiffReportEntry(fromEarlier ? DiffType.DELETE
: DiffType.CREATE, fullPath));
}
}
return list;
}
}