| /** |
| * 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 org.apache.hadoop.classification.InterfaceAudience; |
| import org.apache.hadoop.hdfs.protocol.QuotaExceededException; |
| import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo; |
| import org.apache.hadoop.hdfs.server.namenode.INode; |
| import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo; |
| import org.apache.hadoop.hdfs.server.namenode.INodeFile; |
| import org.apache.hadoop.hdfs.server.namenode.Quota; |
| |
| /** |
| * Feature for file with snapshot-related information. |
| */ |
| @InterfaceAudience.Private |
| public class FileWithSnapshotFeature implements INode.Feature { |
| private final FileDiffList diffs; |
| private boolean isCurrentFileDeleted = false; |
| |
| public FileWithSnapshotFeature(FileDiffList diffs) { |
| this.diffs = diffs != null? diffs: new FileDiffList(); |
| } |
| |
| public boolean isCurrentFileDeleted() { |
| return isCurrentFileDeleted; |
| } |
| |
| /** |
| * We need to distinguish two scenarios: |
| * 1) the file is still in the current file directory, it has been modified |
| * before while it is included in some snapshot |
| * 2) the file is not in the current file directory (deleted), but it is in |
| * some snapshot, thus we still keep this inode |
| * For both scenarios the file has snapshot feature. We set |
| * {@link #isCurrentFileDeleted} to true for 2). |
| */ |
| public void deleteCurrentFile() { |
| isCurrentFileDeleted = true; |
| } |
| |
| public FileDiffList getDiffs() { |
| return diffs; |
| } |
| |
| /** @return the max replication factor in diffs */ |
| public short getMaxBlockRepInDiffs() { |
| short max = 0; |
| for(FileDiff d : getDiffs()) { |
| if (d.snapshotINode != null) { |
| final short replication = d.snapshotINode.getFileReplication(); |
| if (replication > max) { |
| max = replication; |
| } |
| } |
| } |
| return max; |
| } |
| |
| public String getDetailedString() { |
| return (isCurrentFileDeleted()? "(DELETED), ": ", ") + diffs; |
| } |
| |
| public Quota.Counts cleanFile(final INodeFile file, final int snapshotId, |
| int priorSnapshotId, final BlocksMapUpdateInfo collectedBlocks, |
| final List<INode> removedINodes, final boolean countDiffChange) |
| throws QuotaExceededException { |
| if (snapshotId == Snapshot.CURRENT_STATE_ID) { |
| // delete the current file while the file has snapshot feature |
| if (!isCurrentFileDeleted()) { |
| file.recordModification(priorSnapshotId); |
| deleteCurrentFile(); |
| } |
| collectBlocksAndClear(file, collectedBlocks, removedINodes); |
| return Quota.Counts.newInstance(); |
| } else { // delete the snapshot |
| priorSnapshotId = getDiffs().updatePrior(snapshotId, priorSnapshotId); |
| return diffs.deleteSnapshotDiff(snapshotId, priorSnapshotId, file, |
| collectedBlocks, removedINodes, countDiffChange); |
| } |
| } |
| |
| public void clearDiffs() { |
| this.diffs.clear(); |
| } |
| |
| public Quota.Counts updateQuotaAndCollectBlocks(INodeFile file, |
| FileDiff removed, BlocksMapUpdateInfo collectedBlocks, |
| final List<INode> removedINodes) { |
| long oldDiskspace = file.diskspaceConsumed(); |
| if (removed.snapshotINode != null) { |
| short replication = removed.snapshotINode.getFileReplication(); |
| short currentRepl = file.getBlockReplication(); |
| if (currentRepl == 0) { |
| oldDiskspace = file.computeFileSize(true, true) * replication; |
| } else if (replication > currentRepl) { |
| oldDiskspace = oldDiskspace / file.getBlockReplication() * replication; |
| } |
| } |
| |
| collectBlocksAndClear(file, collectedBlocks, removedINodes); |
| |
| long dsDelta = oldDiskspace - file.diskspaceConsumed(); |
| return Quota.Counts.newInstance(0, dsDelta); |
| } |
| |
| /** |
| * If some blocks at the end of the block list no longer belongs to |
| * any inode, collect them and update the block list. |
| */ |
| private void collectBlocksAndClear(final INodeFile file, |
| final BlocksMapUpdateInfo info, final List<INode> removedINodes) { |
| // check if everything is deleted. |
| if (isCurrentFileDeleted() && getDiffs().asList().isEmpty()) { |
| file.destroyAndCollectBlocks(info, removedINodes); |
| return; |
| } |
| // find max file size. |
| final long max; |
| if (isCurrentFileDeleted()) { |
| final FileDiff last = getDiffs().getLast(); |
| max = last == null? 0: last.getFileSize(); |
| } else { |
| max = file.computeFileSize(); |
| } |
| |
| collectBlocksBeyondMax(file, max, info); |
| } |
| |
| private void collectBlocksBeyondMax(final INodeFile file, final long max, |
| final BlocksMapUpdateInfo collectedBlocks) { |
| final BlockInfo[] oldBlocks = file.getBlocks(); |
| if (oldBlocks != null) { |
| //find the minimum n such that the size of the first n blocks > max |
| int n = 0; |
| for(long size = 0; n < oldBlocks.length && max > size; n++) { |
| size += oldBlocks[n].getNumBytes(); |
| } |
| |
| // starting from block n, the data is beyond max. |
| if (n < oldBlocks.length) { |
| // resize the array. |
| final BlockInfo[] newBlocks; |
| if (n == 0) { |
| newBlocks = null; |
| } else { |
| newBlocks = new BlockInfo[n]; |
| System.arraycopy(oldBlocks, 0, newBlocks, 0, n); |
| } |
| |
| // set new blocks |
| file.setBlocks(newBlocks); |
| |
| // collect the blocks beyond max. |
| if (collectedBlocks != null) { |
| for(; n < oldBlocks.length; n++) { |
| collectedBlocks.addDeleteBlock(oldBlocks[n]); |
| } |
| } |
| } |
| } |
| } |
| } |