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.DataInput; 021import java.io.DataOutput; 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.hadoop.hdfs.DFSUtil; 029import org.apache.hadoop.hdfs.server.namenode.FSImageFormat; 030import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization; 031import org.apache.hadoop.hdfs.server.namenode.INode; 032import org.apache.hadoop.hdfs.server.namenode.INodeAttributes; 033import org.apache.hadoop.hdfs.server.namenode.INodeDirectory; 034import org.apache.hadoop.hdfs.server.namenode.INodeDirectoryAttributes; 035import org.apache.hadoop.hdfs.server.namenode.INodeFile; 036import org.apache.hadoop.hdfs.server.namenode.INodeFileAttributes; 037import org.apache.hadoop.hdfs.server.namenode.INodeReference; 038import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiff; 039import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiffList; 040import org.apache.hadoop.hdfs.tools.snapshot.SnapshotDiff; 041import org.apache.hadoop.hdfs.util.Diff.ListType; 042import org.apache.hadoop.hdfs.util.ReadOnlyList; 043 044/** 045 * A helper class defining static methods for reading/writing snapshot related 046 * information from/to FSImage. 047 */ 048public class SnapshotFSImageFormat { 049 /** 050 * Save snapshots and snapshot quota for a snapshottable directory. 051 * @param current The directory that the snapshots belongs to. 052 * @param out The {@link DataOutput} to write. 053 * @throws IOException 054 */ 055 public static void saveSnapshots(INodeDirectorySnapshottable current, 056 DataOutput out) throws IOException { 057 // list of snapshots in snapshotsByNames 058 ReadOnlyList<Snapshot> snapshots = current.getSnapshotsByNames(); 059 out.writeInt(snapshots.size()); 060 for (Snapshot s : snapshots) { 061 // write the snapshot id 062 out.writeInt(s.getId()); 063 } 064 // snapshot quota 065 out.writeInt(current.getSnapshotQuota()); 066 } 067 068 /** 069 * Save SnapshotDiff list for an INodeDirectoryWithSnapshot. 070 * @param sNode The directory that the SnapshotDiff list belongs to. 071 * @param out The {@link DataOutput} to write. 072 */ 073 private static <N extends INode, A extends INodeAttributes, D extends AbstractINodeDiff<N, A, D>> 074 void saveINodeDiffs(final AbstractINodeDiffList<N, A, D> diffs, 075 final DataOutput out, ReferenceMap referenceMap) throws IOException { 076 // Record the diffs in reversed order, so that we can find the correct 077 // reference for INodes in the created list when loading the FSImage 078 if (diffs == null) { 079 out.writeInt(-1); // no diffs 080 } else { 081 final List<D> list = diffs.asList(); 082 final int size = list.size(); 083 out.writeInt(size); 084 for (int i = size - 1; i >= 0; i--) { 085 list.get(i).write(out, referenceMap); 086 } 087 } 088 } 089 090 public static void saveDirectoryDiffList(final INodeDirectory dir, 091 final DataOutput out, final ReferenceMap referenceMap 092 ) throws IOException { 093 saveINodeDiffs(dir.getDiffs(), out, referenceMap); 094 } 095 096 public static void saveFileDiffList(final INodeFile file, 097 final DataOutput out) throws IOException { 098 saveINodeDiffs(file.getDiffs(), out, null); 099 } 100 101 public static FileDiffList loadFileDiffList(DataInput in, 102 FSImageFormat.Loader loader) throws IOException { 103 final int size = in.readInt(); 104 if (size == -1) { 105 return null; 106 } else { 107 final FileDiffList diffs = new FileDiffList(); 108 FileDiff posterior = null; 109 for(int i = 0; i < size; i++) { 110 final FileDiff d = loadFileDiff(posterior, in, loader); 111 diffs.addFirst(d); 112 posterior = d; 113 } 114 return diffs; 115 } 116 } 117 118 private static FileDiff loadFileDiff(FileDiff posterior, DataInput in, 119 FSImageFormat.Loader loader) throws IOException { 120 // 1. Read the id of the Snapshot root to identify the Snapshot 121 final Snapshot snapshot = loader.getSnapshot(in); 122 123 // 2. Load file size 124 final long fileSize = in.readLong(); 125 126 // 3. Load snapshotINode 127 final INodeFileAttributes snapshotINode = in.readBoolean()? 128 loader.loadINodeFileAttributes(in): null; 129 130 return new FileDiff(snapshot.getId(), snapshotINode, posterior, fileSize); 131 } 132 133 /** 134 * Load a node stored in the created list from fsimage. 135 * @param createdNodeName The name of the created node. 136 * @param parent The directory that the created list belongs to. 137 * @return The created node. 138 */ 139 public static INode loadCreated(byte[] createdNodeName, 140 INodeDirectory parent) throws IOException { 141 // the INode in the created list should be a reference to another INode 142 // in posterior SnapshotDiffs or one of the current children 143 for (DirectoryDiff postDiff : parent.getDiffs()) { 144 final INode d = postDiff.getChildrenDiff().search(ListType.DELETED, 145 createdNodeName); 146 if (d != null) { 147 return d; 148 } // else go to the next SnapshotDiff 149 } 150 // use the current child 151 INode currentChild = parent.getChild(createdNodeName, 152 Snapshot.CURRENT_STATE_ID); 153 if (currentChild == null) { 154 throw new IOException("Cannot find an INode associated with the INode " 155 + DFSUtil.bytes2String(createdNodeName) 156 + " in created list while loading FSImage."); 157 } 158 return currentChild; 159 } 160 161 /** 162 * Load the created list from fsimage. 163 * @param parent The directory that the created list belongs to. 164 * @param in The {@link DataInput} to read. 165 * @return The created list. 166 */ 167 private static List<INode> loadCreatedList(INodeDirectory parent, 168 DataInput in) throws IOException { 169 // read the size of the created list 170 int createdSize = in.readInt(); 171 List<INode> createdList = new ArrayList<INode>(createdSize); 172 for (int i = 0; i < createdSize; i++) { 173 byte[] createdNodeName = FSImageSerialization.readLocalName(in); 174 INode created = loadCreated(createdNodeName, parent); 175 createdList.add(created); 176 } 177 return createdList; 178 } 179 180 /** 181 * Load the deleted list from the fsimage. 182 * 183 * @param parent The directory that the deleted list belongs to. 184 * @param createdList The created list associated with the deleted list in 185 * the same Diff. 186 * @param in The {@link DataInput} to read. 187 * @param loader The {@link Loader} instance. 188 * @return The deleted list. 189 */ 190 private static List<INode> loadDeletedList(INodeDirectory parent, 191 List<INode> createdList, DataInput in, FSImageFormat.Loader loader) 192 throws IOException { 193 int deletedSize = in.readInt(); 194 List<INode> deletedList = new ArrayList<INode>(deletedSize); 195 for (int i = 0; i < deletedSize; i++) { 196 final INode deleted = loader.loadINodeWithLocalName(true, in, true); 197 deletedList.add(deleted); 198 // set parent: the parent field of an INode in the deleted list is not 199 // useful, but set the parent here to be consistent with the original 200 // fsdir tree. 201 deleted.setParent(parent); 202 if (deleted.isFile()) { 203 loader.updateBlocksMap(deleted.asFile()); 204 } 205 } 206 return deletedList; 207 } 208 209 /** 210 * Load snapshots and snapshotQuota for a Snapshottable directory. 211 * 212 * @param snapshottableParent 213 * The snapshottable directory for loading. 214 * @param numSnapshots 215 * The number of snapshots that the directory has. 216 * @param loader 217 * The loader 218 */ 219 public static void loadSnapshotList( 220 INodeDirectorySnapshottable snapshottableParent, int numSnapshots, 221 DataInput in, FSImageFormat.Loader loader) throws IOException { 222 for (int i = 0; i < numSnapshots; i++) { 223 // read snapshots 224 final Snapshot s = loader.getSnapshot(in); 225 s.getRoot().setParent(snapshottableParent); 226 snapshottableParent.addSnapshot(s); 227 } 228 int snapshotQuota = in.readInt(); 229 snapshottableParent.setSnapshotQuota(snapshotQuota); 230 } 231 232 /** 233 * Load the {@link SnapshotDiff} list for the INodeDirectoryWithSnapshot 234 * directory. 235 * 236 * @param dir 237 * The snapshottable directory for loading. 238 * @param in 239 * The {@link DataInput} instance to read. 240 * @param loader 241 * The loader 242 */ 243 public static void loadDirectoryDiffList(INodeDirectory dir, 244 DataInput in, FSImageFormat.Loader loader) throws IOException { 245 final int size = in.readInt(); 246 if (dir.isWithSnapshot()) { 247 DirectoryDiffList diffs = dir.getDiffs(); 248 for (int i = 0; i < size; i++) { 249 diffs.addFirst(loadDirectoryDiff(dir, in, loader)); 250 } 251 } 252 } 253 254 /** 255 * Load the snapshotINode field of {@link AbstractINodeDiff}. 256 * @param snapshot The Snapshot associated with the {@link AbstractINodeDiff}. 257 * @param in The {@link DataInput} to read. 258 * @param loader The {@link Loader} instance that this loading procedure is 259 * using. 260 * @return The snapshotINode. 261 */ 262 private static INodeDirectoryAttributes loadSnapshotINodeInDirectoryDiff( 263 Snapshot snapshot, DataInput in, FSImageFormat.Loader loader) 264 throws IOException { 265 // read the boolean indicating whether snapshotINode == Snapshot.Root 266 boolean useRoot = in.readBoolean(); 267 if (useRoot) { 268 return snapshot.getRoot(); 269 } else { 270 // another boolean is used to indicate whether snapshotINode is non-null 271 return in.readBoolean()? loader.loadINodeDirectoryAttributes(in): null; 272 } 273 } 274 275 /** 276 * Load {@link DirectoryDiff} from fsimage. 277 * @param parent The directory that the SnapshotDiff belongs to. 278 * @param in The {@link DataInput} instance to read. 279 * @param loader The {@link Loader} instance that this loading procedure is 280 * using. 281 * @return A {@link DirectoryDiff}. 282 */ 283 private static DirectoryDiff loadDirectoryDiff(INodeDirectory parent, 284 DataInput in, FSImageFormat.Loader loader) throws IOException { 285 // 1. Read the full path of the Snapshot root to identify the Snapshot 286 final Snapshot snapshot = loader.getSnapshot(in); 287 288 // 2. Load DirectoryDiff#childrenSize 289 int childrenSize = in.readInt(); 290 291 // 3. Load DirectoryDiff#snapshotINode 292 INodeDirectoryAttributes snapshotINode = loadSnapshotINodeInDirectoryDiff( 293 snapshot, in, loader); 294 295 // 4. Load the created list in SnapshotDiff#Diff 296 List<INode> createdList = loadCreatedList(parent, in); 297 298 // 5. Load the deleted list in SnapshotDiff#Diff 299 List<INode> deletedList = loadDeletedList(parent, createdList, in, loader); 300 301 // 6. Compose the SnapshotDiff 302 List<DirectoryDiff> diffs = parent.getDiffs().asList(); 303 DirectoryDiff sdiff = new DirectoryDiff(snapshot.getId(), snapshotINode, 304 diffs.isEmpty() ? null : diffs.get(0), childrenSize, createdList, 305 deletedList, snapshotINode == snapshot.getRoot()); 306 return sdiff; 307 } 308 309 310 /** A reference map for fsimage serialization. */ 311 public static class ReferenceMap { 312 /** 313 * Used to indicate whether the reference node itself has been saved 314 */ 315 private final Map<Long, INodeReference.WithCount> referenceMap 316 = new HashMap<Long, INodeReference.WithCount>(); 317 /** 318 * Used to record whether the subtree of the reference node has been saved 319 */ 320 private final Map<Long, Long> dirMap = new HashMap<Long, Long>(); 321 322 public void writeINodeReferenceWithCount( 323 INodeReference.WithCount withCount, DataOutput out, 324 boolean writeUnderConstruction) throws IOException { 325 final INode referred = withCount.getReferredINode(); 326 final long id = withCount.getId(); 327 final boolean firstReferred = !referenceMap.containsKey(id); 328 out.writeBoolean(firstReferred); 329 330 if (firstReferred) { 331 FSImageSerialization.saveINode2Image(referred, out, 332 writeUnderConstruction, this); 333 referenceMap.put(id, withCount); 334 } else { 335 out.writeLong(id); 336 } 337 } 338 339 public boolean toProcessSubtree(long id) { 340 if (dirMap.containsKey(id)) { 341 return false; 342 } else { 343 dirMap.put(id, id); 344 return true; 345 } 346 } 347 348 public INodeReference.WithCount loadINodeReferenceWithCount( 349 boolean isSnapshotINode, DataInput in, FSImageFormat.Loader loader 350 ) throws IOException { 351 final boolean firstReferred = in.readBoolean(); 352 353 final INodeReference.WithCount withCount; 354 if (firstReferred) { 355 final INode referred = loader.loadINodeWithLocalName(isSnapshotINode, 356 in, true); 357 withCount = new INodeReference.WithCount(null, referred); 358 referenceMap.put(withCount.getId(), withCount); 359 } else { 360 final long id = in.readLong(); 361 withCount = referenceMap.get(id); 362 } 363 return withCount; 364 } 365 } 366}