| /** |
| * 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()); |
| } |
| } |