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}