001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hdfs.server.namenode.snapshot; 019 020import java.io.DataOutput; 021import java.io.IOException; 022import java.util.List; 023 024import org.apache.hadoop.classification.InterfaceAudience; 025import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo; 026import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization; 027import org.apache.hadoop.hdfs.server.namenode.INode; 028import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo; 029import org.apache.hadoop.hdfs.server.namenode.INodeFile; 030import org.apache.hadoop.hdfs.server.namenode.INodeFileAttributes; 031import org.apache.hadoop.hdfs.server.namenode.Quota; 032import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotFSImageFormat.ReferenceMap; 033 034/** 035 * An interface for {@link INodeFile} to support snapshot. 036 */ 037@InterfaceAudience.Private 038public interface FileWithSnapshot { 039 /** 040 * The difference of an {@link INodeFile} between two snapshots. 041 */ 042 public static class FileDiff extends AbstractINodeDiff<INodeFile, INodeFileAttributes, FileDiff> { 043 /** The file size at snapshot creation time. */ 044 private final long fileSize; 045 046 private FileDiff(Snapshot snapshot, INodeFile file) { 047 super(snapshot, null, null); 048 fileSize = file.computeFileSize(); 049 } 050 051 /** Constructor used by FSImage loading */ 052 FileDiff(Snapshot snapshot, INodeFileAttributes snapshotINode, 053 FileDiff posteriorDiff, long fileSize) { 054 super(snapshot, snapshotINode, posteriorDiff); 055 this.fileSize = fileSize; 056 } 057 058 /** @return the file size in the snapshot. */ 059 public long getFileSize() { 060 return fileSize; 061 } 062 063 private static Quota.Counts updateQuotaAndCollectBlocks( 064 INodeFile currentINode, FileDiff removed, 065 BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) { 066 FileWithSnapshot sFile = (FileWithSnapshot) currentINode; 067 long oldDiskspace = currentINode.diskspaceConsumed(); 068 if (removed.snapshotINode != null) { 069 short replication = removed.snapshotINode.getFileReplication(); 070 short currentRepl = currentINode.getBlockReplication(); 071 if (currentRepl == 0) { 072 oldDiskspace = currentINode.computeFileSize(true, true) * replication; 073 } else if (replication > currentRepl) { 074 oldDiskspace = oldDiskspace / currentINode.getBlockReplication() 075 * replication; 076 } 077 } 078 079 Util.collectBlocksAndClear(sFile, collectedBlocks, removedINodes); 080 081 long dsDelta = oldDiskspace - currentINode.diskspaceConsumed(); 082 return Quota.Counts.newInstance(0, dsDelta); 083 } 084 085 @Override 086 Quota.Counts combinePosteriorAndCollectBlocks(INodeFile currentINode, 087 FileDiff posterior, BlocksMapUpdateInfo collectedBlocks, 088 final List<INode> removedINodes) { 089 return updateQuotaAndCollectBlocks(currentINode, posterior, 090 collectedBlocks, removedINodes); 091 } 092 093 @Override 094 public String toString() { 095 return super.toString() + " fileSize=" + fileSize + ", rep=" 096 + (snapshotINode == null? "?": snapshotINode.getFileReplication()); 097 } 098 099 @Override 100 void write(DataOutput out, ReferenceMap referenceMap) throws IOException { 101 writeSnapshot(out); 102 out.writeLong(fileSize); 103 104 // write snapshotINode 105 if (snapshotINode != null) { 106 out.writeBoolean(true); 107 FSImageSerialization.writeINodeFileAttributes(snapshotINode, out); 108 } else { 109 out.writeBoolean(false); 110 } 111 } 112 113 @Override 114 Quota.Counts destroyDiffAndCollectBlocks(INodeFile currentINode, 115 BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) { 116 return updateQuotaAndCollectBlocks(currentINode, this, 117 collectedBlocks, removedINodes); 118 } 119 } 120 121 /** A list of FileDiffs for storing snapshot data. */ 122 public static class FileDiffList 123 extends AbstractINodeDiffList<INodeFile, INodeFileAttributes, FileDiff> { 124 125 @Override 126 FileDiff createDiff(Snapshot snapshot, INodeFile file) { 127 return new FileDiff(snapshot, file); 128 } 129 130 @Override 131 INodeFileAttributes createSnapshotCopy(INodeFile currentINode) { 132 return new INodeFileAttributes.SnapshotCopy(currentINode); 133 } 134 } 135 136 /** @return the {@link INodeFile} view of this object. */ 137 public INodeFile asINodeFile(); 138 139 /** @return the file diff list. */ 140 public FileDiffList getDiffs(); 141 142 /** Is the current file deleted? */ 143 public boolean isCurrentFileDeleted(); 144 145 /** Delete the file from the current tree */ 146 public void deleteCurrentFile(); 147 148 /** Utility methods for the classes which implement the interface. */ 149 public static class Util { 150 /** 151 * @return block replication, which is the max file replication among 152 * the file and the diff list. 153 */ 154 public static short getBlockReplication(final FileWithSnapshot file) { 155 short max = file.isCurrentFileDeleted()? 0 156 : file.asINodeFile().getFileReplication(); 157 for(FileDiff d : file.getDiffs()) { 158 if (d.snapshotINode != null) { 159 final short replication = d.snapshotINode.getFileReplication(); 160 if (replication > max) { 161 max = replication; 162 } 163 } 164 } 165 return max; 166 } 167 168 /** 169 * If some blocks at the end of the block list no longer belongs to 170 * any inode, collect them and update the block list. 171 */ 172 static void collectBlocksAndClear(final FileWithSnapshot file, 173 final BlocksMapUpdateInfo info, final List<INode> removedINodes) { 174 // check if everything is deleted. 175 if (file.isCurrentFileDeleted() 176 && file.getDiffs().asList().isEmpty()) { 177 file.asINodeFile().destroyAndCollectBlocks(info, removedINodes); 178 return; 179 } 180 181 // find max file size. 182 final long max; 183 if (file.isCurrentFileDeleted()) { 184 final FileDiff last = file.getDiffs().getLast(); 185 max = last == null? 0: last.fileSize; 186 } else { 187 max = file.asINodeFile().computeFileSize(); 188 } 189 190 collectBlocksBeyondMax(file, max, info); 191 } 192 193 private static void collectBlocksBeyondMax(final FileWithSnapshot file, 194 final long max, final BlocksMapUpdateInfo collectedBlocks) { 195 final BlockInfo[] oldBlocks = file.asINodeFile().getBlocks(); 196 if (oldBlocks != null) { 197 //find the minimum n such that the size of the first n blocks > max 198 int n = 0; 199 for(long size = 0; n < oldBlocks.length && max > size; n++) { 200 size += oldBlocks[n].getNumBytes(); 201 } 202 203 // starting from block n, the data is beyond max. 204 if (n < oldBlocks.length) { 205 // resize the array. 206 final BlockInfo[] newBlocks; 207 if (n == 0) { 208 newBlocks = null; 209 } else { 210 newBlocks = new BlockInfo[n]; 211 System.arraycopy(oldBlocks, 0, newBlocks, 0, n); 212 } 213 214 // set new blocks 215 file.asINodeFile().setBlocks(newBlocks); 216 217 // collect the blocks beyond max. 218 if (collectedBlocks != null) { 219 for(; n < oldBlocks.length; n++) { 220 collectedBlocks.addDeleteBlock(oldBlocks[n]); 221 } 222 } 223 } 224 } 225 } 226 } 227}