blob: 4b03c4f0d53a1589d14a8524587d6ab654ddf427 [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.List;
import java.util.ListIterator;
import org.apache.hadoop.hdfs.DFSUtilClient;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReportListing;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReportListing.DiffReportListingEntry;
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.util.ChunkedArrayList;
/**
* A class describing the difference between snapshots of a snapshottable
* directory where the difference is limited by dfs.snapshotDiff-report.limit.
*/
class SnapshotDiffListingInfo {
private final int maxEntries;
/** 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;
/** The path of the file to start for computing the snapshot diff. */
private byte[] lastPath = DFSUtilClient.EMPTY_BYTES;
private int lastIndex = -1;
/*
* A list containing all the modified entries between the given snapshots
* within a single rpc call.
*/
private final List<DiffReportListingEntry> modifiedList =
new ChunkedArrayList<>();
private final List<DiffReportListingEntry> createdList =
new ChunkedArrayList<>();
private final List<DiffReportListingEntry> deletedList =
new ChunkedArrayList<>();
SnapshotDiffListingInfo(INodeDirectory snapshotRootDir,
INodeDirectory snapshotDiffScopeDir, Snapshot start, Snapshot end,
int snapshotDiffReportLimit) {
Preconditions.checkArgument(
snapshotRootDir.isSnapshottable() && snapshotDiffScopeDir
.isDescendantOfSnapshotRoot(snapshotRootDir));
this.snapshotRoot = snapshotRootDir;
this.snapshotDiffScopeDir = snapshotDiffScopeDir;
this.from = start;
this.to = end;
this.maxEntries = snapshotDiffReportLimit;
}
boolean addDirDiff(long dirId, byte[][] parent, ChildrenDiff diff) {
final Snapshot laterSnapshot = getLater();
if (lastIndex == -1) {
if (getTotalEntries() < maxEntries) {
modifiedList.add(new DiffReportListingEntry(
dirId, dirId, parent, true, null));
} else {
setLastPath(parent);
setLastIndex(-1);
return false;
}
}
final List<INode> clist = diff.getCreatedUnmodifiable();
if (lastIndex == -1 || lastIndex < clist.size()) {
ListIterator<INode> iterator = lastIndex != -1 ?
clist.listIterator(lastIndex): clist.listIterator();
while (iterator.hasNext()) {
if (getTotalEntries() < maxEntries) {
INode created = iterator.next();
byte[][] path = newPath(parent, created.getLocalNameBytes());
createdList.add(new DiffReportListingEntry(dirId, created.getId(),
path, created.isReference(), null));
} else {
setLastPath(parent);
setLastIndex(iterator.nextIndex());
return false;
}
}
setLastIndex(-1);
}
if (lastIndex == -1 || lastIndex >= clist.size()) {
final List<INode> dlist = diff.getDeletedUnmodifiable();
int size = clist.size();
ListIterator<INode> iterator = lastIndex != -1 ?
dlist.listIterator(lastIndex - size): dlist.listIterator();
while (iterator.hasNext()) {
if (getTotalEntries() < maxEntries) {
final INode d = iterator.next();
byte[][] path = newPath(parent, d.getLocalNameBytes());
byte[][] target = findRenameTargetPath(d, laterSnapshot);
final DiffReportListingEntry e = target != null ?
new DiffReportListingEntry(dirId, d.getId(), path, true, target) :
new DiffReportListingEntry(dirId, d.getId(), path, false, null);
deletedList.add(e);
} else {
setLastPath(parent);
// the offset will be set to created list + iterator index in the
// deleted list so that it points to the exact entry in the deleted
// list post checking the created list in the next iteration of rpc
// call
setLastIndex(size + iterator.nextIndex());
return false;
}
}
setLastIndex(-1);
}
return true;
}
private byte[][] findRenameTargetPath(INode deleted, Snapshot laterSnapshot) {
if (deleted instanceof INodeReference.WithName) {
return snapshotRoot.getDirectorySnapshottableFeature()
.findRenameTargetPath(snapshotDiffScopeDir,
(INodeReference.WithName) deleted,
Snapshot.getSnapshotId(laterSnapshot));
}
return null;
}
private static byte[][] newPath(byte[][] parent, byte[] name) {
byte[][] fullPath = new byte[parent.length + 1][];
System.arraycopy(parent, 0, fullPath, 0, parent.length);
fullPath[fullPath.length - 1] = name;
return fullPath;
}
Snapshot getEarlier() {
return isFromEarlier()? from: to;
}
Snapshot getLater() {
return isFromEarlier()? to: from;
}
public void setLastPath(byte[][] lastPath) {
this.lastPath = DFSUtilClient.byteArray2bytes(lastPath);
}
public void setLastIndex(int idx) {
this.lastIndex = idx;
}
boolean addFileDiff(INodeFile file, byte[][] relativePath) {
if (getTotalEntries() < maxEntries) {
modifiedList.add(new DiffReportListingEntry(file.getId(),
file.getId(), relativePath,false, null));
} else {
setLastPath(relativePath);
return false;
}
return true;
}
/** @return True if {@link #from} is earlier than {@link #to} */
boolean isFromEarlier() {
return Snapshot.ID_COMPARATOR.compare(from, to) < 0;
}
private int getTotalEntries() {
return createdList.size() + modifiedList.size() + deletedList.size();
}
/**
* Generate a {@link SnapshotDiffReportListing} based on detailed diff
* information.
*
* @return A {@link SnapshotDiffReportListing} describing the difference
*/
public SnapshotDiffReportListing generateReport() {
return new SnapshotDiffReportListing(lastPath, modifiedList, createdList,
deletedList, lastIndex, isFromEarlier());
}
}