blob: a576c571d084f75ca6de0c22198f3c26527daa1a [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.ArrayList;
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.hdfs.util.Diff.ListType;
import com.google.common.base.Preconditions;
import com.google.common.primitives.SignedBytes;
/**
* 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 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>();
SnapshotDiffInfo(INodeDirectory snapshotRoot, Snapshot start, Snapshot end) {
Preconditions.checkArgument(snapshotRoot.isSnapshottable());
this.snapshotRoot = snapshotRoot;
this.from = start;
this.to = end;
}
/** 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.getList(ListType.CREATED)) {
if (created.isReference()) {
RenameEntry entry = getEntry(created.getId());
if (entry.getTargetPath() == null) {
entry.setTarget(created, relativePath);
}
}
}
for (INode deleted : diff.getList(ListType.DELETED)) {
if (deleted instanceof INodeReference.WithName) {
RenameEntry entry = getEntry(deleted.getId());
entry.setSource(deleted, relativePath);
}
}
}
Snapshot getFrom() {
return from;
}
Snapshot getTo() {
return to;
}
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 ArrayList<DiffReportEntry>();
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);
}
}
return new SnapshotDiffReport(snapshotRoot.getFullPathName(),
Snapshot.getSnapshotName(from), Snapshot.getSnapshotName(to),
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 ArrayList<DiffReportEntry>();
List<INode> created = dirDiff.getList(ListType.CREATED);
List<INode> deleted = dirDiff.getList(ListType.DELETED);
byte[][] fullPath = new byte[parentPath.length + 1][];
System.arraycopy(parentPath, 0, fullPath, 0, parentPath.length);
for (INode cnode : created) {
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 : deleted) {
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;
}
}