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 static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.loadINodeDirectory;
021import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.loadPermission;
022import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.updateBlocksMap;
023import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Saver.buildINodeDirectory;
024import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Saver.buildINodeFile;
025
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.OutputStream;
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.Comparator;
032import java.util.HashMap;
033import java.util.Iterator;
034import java.util.List;
035import java.util.Map;
036
037import org.apache.hadoop.classification.InterfaceAudience;
038import org.apache.hadoop.fs.permission.PermissionStatus;
039import org.apache.hadoop.hdfs.server.namenode.AclFeature;
040import org.apache.hadoop.hdfs.server.namenode.FSDirectory;
041import org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode;
042import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf;
043import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.LoaderContext;
044import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.SectionName;
045import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
046import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary;
047import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeReferenceSection;
048import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection;
049import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection;
050import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection.CreatedListEntry;
051import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection.DiffEntry.Type;
052import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotSection;
053import org.apache.hadoop.hdfs.server.namenode.INode;
054import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
055import org.apache.hadoop.hdfs.server.namenode.INodeDirectoryAttributes;
056import org.apache.hadoop.hdfs.server.namenode.INodeFile;
057import org.apache.hadoop.hdfs.server.namenode.INodeFileAttributes;
058import org.apache.hadoop.hdfs.server.namenode.INodeMap;
059import org.apache.hadoop.hdfs.server.namenode.INodeReference;
060import org.apache.hadoop.hdfs.server.namenode.INodeReference.DstReference;
061import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount;
062import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithName;
063import org.apache.hadoop.hdfs.server.namenode.INodeWithAdditionalFields;
064import org.apache.hadoop.hdfs.server.namenode.SaveNamespaceContext;
065import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiff;
066import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiffList;
067import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot.Root;
068import org.apache.hadoop.hdfs.server.namenode.XAttrFeature;
069import org.apache.hadoop.hdfs.util.Diff.ListType;
070
071import com.google.common.base.Preconditions;
072import com.google.protobuf.ByteString;
073
074@InterfaceAudience.Private
075public class FSImageFormatPBSnapshot {
076  /**
077   * Loading snapshot related information from protobuf based FSImage
078   */
079  public final static class Loader {
080    private final FSNamesystem fsn;
081    private final FSDirectory fsDir;
082    private final FSImageFormatProtobuf.Loader parent;
083    private final Map<Integer, Snapshot> snapshotMap;
084
085    public Loader(FSNamesystem fsn, FSImageFormatProtobuf.Loader parent) {
086      this.fsn = fsn;
087      this.fsDir = fsn.getFSDirectory();
088      this.snapshotMap = new HashMap<Integer, Snapshot>();
089      this.parent = parent;
090    }
091
092    /**
093     * The sequence of the ref node in refList must be strictly the same with
094     * the sequence in fsimage
095     */
096    public void loadINodeReferenceSection(InputStream in) throws IOException {
097      final List<INodeReference> refList = parent.getLoaderContext()
098          .getRefList();
099      while (true) {
100        INodeReferenceSection.INodeReference e = INodeReferenceSection
101            .INodeReference.parseDelimitedFrom(in);
102        if (e == null) {
103          break;
104        }
105        INodeReference ref = loadINodeReference(e);
106        refList.add(ref);
107      }
108    }
109
110    private INodeReference loadINodeReference(
111        INodeReferenceSection.INodeReference r) throws IOException {
112      long referredId = r.getReferredId();
113      INode referred = fsDir.getInode(referredId);
114      WithCount withCount = (WithCount) referred.getParentReference();
115      if (withCount == null) {
116        withCount = new INodeReference.WithCount(null, referred);
117      }
118      final INodeReference ref;
119      if (r.hasDstSnapshotId()) { // DstReference
120        ref = new INodeReference.DstReference(null, withCount,
121            r.getDstSnapshotId());
122      } else {
123        ref = new INodeReference.WithName(null, withCount, r.getName()
124            .toByteArray(), r.getLastSnapshotId());
125      }
126      return ref;
127    }
128
129    /**
130     * Load the snapshots section from fsimage. Also convert snapshottable
131     * directories into {@link INodeDirectorySnapshottable}.
132     *
133     */
134    public void loadSnapshotSection(InputStream in) throws IOException {
135      SnapshotManager sm = fsn.getSnapshotManager();
136      SnapshotSection section = SnapshotSection.parseDelimitedFrom(in);
137      int snum = section.getNumSnapshots();
138      sm.setNumSnapshots(snum);
139      sm.setSnapshotCounter(section.getSnapshotCounter());
140      for (long sdirId : section.getSnapshottableDirList()) {
141        INodeDirectory dir = fsDir.getInode(sdirId).asDirectory();
142        final INodeDirectorySnapshottable sdir;
143        if (!dir.isSnapshottable()) {
144          sdir = new INodeDirectorySnapshottable(dir);
145          fsDir.addToInodeMap(sdir);
146        } else {
147          // dir is root, and admin set root to snapshottable before
148          sdir = (INodeDirectorySnapshottable) dir;
149          sdir.setSnapshotQuota(INodeDirectorySnapshottable.SNAPSHOT_LIMIT);
150        }
151        sm.addSnapshottable(sdir);
152      }
153      loadSnapshots(in, snum);
154    }
155
156    private void loadSnapshots(InputStream in, int size) throws IOException {
157      for (int i = 0; i < size; i++) {
158        SnapshotSection.Snapshot pbs = SnapshotSection.Snapshot
159            .parseDelimitedFrom(in);
160        INodeDirectory root = loadINodeDirectory(pbs.getRoot(),
161            parent.getLoaderContext());
162        int sid = pbs.getSnapshotId();
163        INodeDirectorySnapshottable parent = (INodeDirectorySnapshottable) fsDir
164            .getInode(root.getId()).asDirectory();
165        Snapshot snapshot = new Snapshot(sid, root, parent);
166        // add the snapshot to parent, since we follow the sequence of
167        // snapshotsByNames when saving, we do not need to sort when loading
168        parent.addSnapshot(snapshot);
169        snapshotMap.put(sid, snapshot);
170      }
171    }
172
173    /**
174     * Load the snapshot diff section from fsimage.
175     */
176    public void loadSnapshotDiffSection(InputStream in) throws IOException {
177      final List<INodeReference> refList = parent.getLoaderContext()
178          .getRefList();
179      while (true) {
180        SnapshotDiffSection.DiffEntry entry = SnapshotDiffSection.DiffEntry
181            .parseDelimitedFrom(in);
182        if (entry == null) {
183          break;
184        }
185        long inodeId = entry.getInodeId();
186        INode inode = fsDir.getInode(inodeId);
187        SnapshotDiffSection.DiffEntry.Type type = entry.getType();
188        switch (type) {
189        case FILEDIFF:
190          loadFileDiffList(in, inode.asFile(), entry.getNumOfDiff());
191          break;
192        case DIRECTORYDIFF:
193          loadDirectoryDiffList(in, inode.asDirectory(), entry.getNumOfDiff(),
194              refList);
195          break;
196        }
197      }
198    }
199
200    /** Load FileDiff list for a file with snapshot feature */
201    private void loadFileDiffList(InputStream in, INodeFile file, int size)
202        throws IOException {
203      final FileDiffList diffs = new FileDiffList();
204      final LoaderContext state = parent.getLoaderContext();
205      for (int i = 0; i < size; i++) {
206        SnapshotDiffSection.FileDiff pbf = SnapshotDiffSection.FileDiff
207            .parseDelimitedFrom(in);
208        INodeFileAttributes copy = null;
209        if (pbf.hasSnapshotCopy()) {
210          INodeSection.INodeFile fileInPb = pbf.getSnapshotCopy();
211          PermissionStatus permission = loadPermission(
212              fileInPb.getPermission(), state.getStringTable());
213
214          AclFeature acl = null;
215          if (fileInPb.hasAcl()) {
216            acl = new AclFeature(FSImageFormatPBINode.Loader.loadAclEntries(
217                fileInPb.getAcl(), state.getStringTable()));
218          }
219          XAttrFeature xAttrs = null;
220          if (fileInPb.hasXAttrs()) {
221            xAttrs = new XAttrFeature(FSImageFormatPBINode.Loader.loadXAttrs(
222                fileInPb.getXAttrs(), state.getStringTable()));
223          }
224
225          copy = new INodeFileAttributes.SnapshotCopy(pbf.getName()
226              .toByteArray(), permission, acl, fileInPb.getModificationTime(),
227              fileInPb.getAccessTime(), (short) fileInPb.getReplication(),
228              fileInPb.getPreferredBlockSize(), xAttrs);
229        }
230
231        FileDiff diff = new FileDiff(pbf.getSnapshotId(), copy, null,
232            pbf.getFileSize());
233        diffs.addFirst(diff);
234      }
235      file.addSnapshotFeature(diffs);
236    }
237
238    /** Load the created list in a DirectoryDiff */
239    private List<INode> loadCreatedList(InputStream in, INodeDirectory dir,
240        int size) throws IOException {
241      List<INode> clist = new ArrayList<INode>(size);
242      for (long c = 0; c < size; c++) {
243        CreatedListEntry entry = CreatedListEntry.parseDelimitedFrom(in);
244        INode created = SnapshotFSImageFormat.loadCreated(entry.getName()
245            .toByteArray(), dir);
246        clist.add(created);
247      }
248      return clist;
249    }
250
251    private void addToDeletedList(INode dnode, INodeDirectory parent) {
252      dnode.setParent(parent);
253      if (dnode.isFile()) {
254        updateBlocksMap(dnode.asFile(), fsn.getBlockManager());
255      }
256    }
257
258    /**
259     * Load the deleted list in a DirectoryDiff
260     */
261    private List<INode> loadDeletedList(final List<INodeReference> refList,
262        InputStream in, INodeDirectory dir, List<Long> deletedNodes,
263        List<Integer> deletedRefNodes)
264        throws IOException {
265      List<INode> dlist = new ArrayList<INode>(deletedRefNodes.size()
266          + deletedNodes.size());
267      // load non-reference inodes
268      for (long deletedId : deletedNodes) {
269        INode deleted = fsDir.getInode(deletedId);
270        dlist.add(deleted);
271        addToDeletedList(deleted, dir);
272      }
273      // load reference nodes in the deleted list
274      for (int refId : deletedRefNodes) {
275        INodeReference deletedRef = refList.get(refId);
276        dlist.add(deletedRef);
277        addToDeletedList(deletedRef, dir);
278      }
279
280      Collections.sort(dlist, new Comparator<INode>() {
281        @Override
282        public int compare(INode n1, INode n2) {
283          return n1.compareTo(n2.getLocalNameBytes());
284        }
285      });
286      return dlist;
287    }
288
289    /** Load DirectoryDiff list for a directory with snapshot feature */
290    private void loadDirectoryDiffList(InputStream in, INodeDirectory dir,
291        int size, final List<INodeReference> refList) throws IOException {
292      if (!dir.isWithSnapshot()) {
293        dir.addSnapshotFeature(null);
294      }
295      DirectoryDiffList diffs = dir.getDiffs();
296      final LoaderContext state = parent.getLoaderContext();
297
298      for (int i = 0; i < size; i++) {
299        // load a directory diff
300        SnapshotDiffSection.DirectoryDiff diffInPb = SnapshotDiffSection.
301            DirectoryDiff.parseDelimitedFrom(in);
302        final int snapshotId = diffInPb.getSnapshotId();
303        final Snapshot snapshot = snapshotMap.get(snapshotId);
304        int childrenSize = diffInPb.getChildrenSize();
305        boolean useRoot = diffInPb.getIsSnapshotRoot();
306        INodeDirectoryAttributes copy = null;
307        if (useRoot) {
308          copy = snapshot.getRoot();
309        } else if (diffInPb.hasSnapshotCopy()) {
310          INodeSection.INodeDirectory dirCopyInPb = diffInPb.getSnapshotCopy();
311          final byte[] name = diffInPb.getName().toByteArray();
312          PermissionStatus permission = loadPermission(
313              dirCopyInPb.getPermission(), state.getStringTable());
314          AclFeature acl = null;
315          if (dirCopyInPb.hasAcl()) {
316            acl = new AclFeature(FSImageFormatPBINode.Loader.loadAclEntries(
317                dirCopyInPb.getAcl(), state.getStringTable()));
318          }
319          XAttrFeature xAttrs = null;
320          if (dirCopyInPb.hasXAttrs()) {
321            xAttrs = new XAttrFeature(FSImageFormatPBINode.Loader.loadXAttrs(
322                dirCopyInPb.getXAttrs(), state.getStringTable()));
323          }
324
325          long modTime = dirCopyInPb.getModificationTime();
326          boolean noQuota = dirCopyInPb.getNsQuota() == -1
327              && dirCopyInPb.getDsQuota() == -1;
328
329          copy = noQuota ? new INodeDirectoryAttributes.SnapshotCopy(name,
330              permission, acl, modTime, xAttrs)
331              : new INodeDirectoryAttributes.CopyWithQuota(name, permission,
332                  acl, modTime, dirCopyInPb.getNsQuota(),
333                  dirCopyInPb.getDsQuota(), xAttrs);
334        }
335        // load created list
336        List<INode> clist = loadCreatedList(in, dir,
337            diffInPb.getCreatedListSize());
338        // load deleted list
339        List<INode> dlist = loadDeletedList(refList, in, dir,
340            diffInPb.getDeletedINodeList(), diffInPb.getDeletedINodeRefList());
341        // create the directory diff
342        DirectoryDiff diff = new DirectoryDiff(snapshotId, copy, null,
343            childrenSize, clist, dlist, useRoot);
344        diffs.addFirst(diff);
345      }
346    }
347  }
348
349  /**
350   * Saving snapshot related information to protobuf based FSImage
351   */
352  public final static class Saver {
353    private final FSNamesystem fsn;
354    private final FileSummary.Builder headers;
355    private final FSImageFormatProtobuf.Saver parent;
356    private final SaveNamespaceContext context;
357
358    public Saver(FSImageFormatProtobuf.Saver parent,
359        FileSummary.Builder headers, SaveNamespaceContext context,
360        FSNamesystem fsn) {
361      this.parent = parent;
362      this.headers = headers;
363      this.context = context;
364      this.fsn = fsn;
365    }
366
367    /**
368     * save all the snapshottable directories and snapshots to fsimage
369     */
370    public void serializeSnapshotSection(OutputStream out) throws IOException {
371      SnapshotManager sm = fsn.getSnapshotManager();
372      SnapshotSection.Builder b = SnapshotSection.newBuilder()
373          .setSnapshotCounter(sm.getSnapshotCounter())
374          .setNumSnapshots(sm.getNumSnapshots());
375
376      INodeDirectorySnapshottable[] snapshottables = sm.getSnapshottableDirs();
377      for (INodeDirectorySnapshottable sdir : snapshottables) {
378        b.addSnapshottableDir(sdir.getId());
379      }
380      b.build().writeDelimitedTo(out);
381      int i = 0;
382      for(INodeDirectorySnapshottable sdir : snapshottables) {
383        for(Snapshot s : sdir.getSnapshotsByNames()) {
384          Root sroot = s.getRoot();
385          SnapshotSection.Snapshot.Builder sb = SnapshotSection.Snapshot
386              .newBuilder().setSnapshotId(s.getId());
387          INodeSection.INodeDirectory.Builder db = buildINodeDirectory(sroot,
388              parent.getSaverContext());
389          INodeSection.INode r = INodeSection.INode.newBuilder()
390              .setId(sroot.getId())
391              .setType(INodeSection.INode.Type.DIRECTORY)
392              .setName(ByteString.copyFrom(sroot.getLocalNameBytes()))
393              .setDirectory(db).build();
394          sb.setRoot(r).build().writeDelimitedTo(out);
395          i++;
396          if (i % FSImageFormatProtobuf.Saver.CHECK_CANCEL_INTERVAL == 0) {
397            context.checkCancelled();
398          }
399        }
400      }
401      Preconditions.checkState(i == sm.getNumSnapshots());
402      parent.commitSection(headers, FSImageFormatProtobuf.SectionName.SNAPSHOT);
403    }
404
405    /**
406     * This can only be called after serializing both INode_Dir and SnapshotDiff
407     */
408    public void serializeINodeReferenceSection(OutputStream out)
409        throws IOException {
410      final List<INodeReference> refList = parent.getSaverContext()
411          .getRefList();
412      for (INodeReference ref : refList) {
413        INodeReferenceSection.INodeReference.Builder rb = buildINodeReference(ref);
414        rb.build().writeDelimitedTo(out);
415      }
416      parent.commitSection(headers, SectionName.INODE_REFERENCE);
417    }
418
419    private INodeReferenceSection.INodeReference.Builder buildINodeReference(
420        INodeReference ref) throws IOException {
421      INodeReferenceSection.INodeReference.Builder rb =
422          INodeReferenceSection.INodeReference.newBuilder().
423            setReferredId(ref.getId());
424      if (ref instanceof WithName) {
425        rb.setLastSnapshotId(((WithName) ref).getLastSnapshotId()).setName(
426            ByteString.copyFrom(ref.getLocalNameBytes()));
427      } else if (ref instanceof DstReference) {
428        rb.setDstSnapshotId(ref.getDstSnapshotId());
429      }
430      return rb;
431    }
432
433    /**
434     * save all the snapshot diff to fsimage
435     */
436    public void serializeSnapshotDiffSection(OutputStream out)
437        throws IOException {
438      INodeMap inodesMap = fsn.getFSDirectory().getINodeMap();
439      final List<INodeReference> refList = parent.getSaverContext()
440          .getRefList();
441      int i = 0;
442      Iterator<INodeWithAdditionalFields> iter = inodesMap.getMapIterator();
443      while (iter.hasNext()) {
444        INodeWithAdditionalFields inode = iter.next();
445        if (inode.isFile()) {
446          serializeFileDiffList(inode.asFile(), out);
447        } else if (inode.isDirectory()) {
448          serializeDirDiffList(inode.asDirectory(), refList, out);
449        }
450        ++i;
451        if (i % FSImageFormatProtobuf.Saver.CHECK_CANCEL_INTERVAL == 0) {
452          context.checkCancelled();
453        }
454      }
455      parent.commitSection(headers,
456          FSImageFormatProtobuf.SectionName.SNAPSHOT_DIFF);
457    }
458
459    private void serializeFileDiffList(INodeFile file, OutputStream out)
460        throws IOException {
461      FileWithSnapshotFeature sf = file.getFileWithSnapshotFeature();
462      if (sf != null) {
463        List<FileDiff> diffList = sf.getDiffs().asList();
464        SnapshotDiffSection.DiffEntry entry = SnapshotDiffSection.DiffEntry
465            .newBuilder().setInodeId(file.getId()).setType(Type.FILEDIFF)
466            .setNumOfDiff(diffList.size()).build();
467        entry.writeDelimitedTo(out);
468        for (int i = diffList.size() - 1; i >= 0; i--) {
469          FileDiff diff = diffList.get(i);
470          SnapshotDiffSection.FileDiff.Builder fb = SnapshotDiffSection.FileDiff
471              .newBuilder().setSnapshotId(diff.getSnapshotId())
472              .setFileSize(diff.getFileSize());
473          INodeFileAttributes copy = diff.snapshotINode;
474          if (copy != null) {
475            fb.setName(ByteString.copyFrom(copy.getLocalNameBytes()))
476                .setSnapshotCopy(buildINodeFile(copy, parent.getSaverContext()));
477          }
478          fb.build().writeDelimitedTo(out);
479        }
480      }
481    }
482
483    private void saveCreatedList(List<INode> created, OutputStream out)
484        throws IOException {
485      // local names of the created list member
486      for (INode c : created) {
487        SnapshotDiffSection.CreatedListEntry.newBuilder()
488            .setName(ByteString.copyFrom(c.getLocalNameBytes())).build()
489            .writeDelimitedTo(out);
490      }
491    }
492
493    private void serializeDirDiffList(INodeDirectory dir,
494        final List<INodeReference> refList, OutputStream out)
495        throws IOException {
496      DirectoryWithSnapshotFeature sf = dir.getDirectoryWithSnapshotFeature();
497      if (sf != null) {
498        List<DirectoryDiff> diffList = sf.getDiffs().asList();
499        SnapshotDiffSection.DiffEntry entry = SnapshotDiffSection.DiffEntry
500            .newBuilder().setInodeId(dir.getId()).setType(Type.DIRECTORYDIFF)
501            .setNumOfDiff(diffList.size()).build();
502        entry.writeDelimitedTo(out);
503        for (int i = diffList.size() - 1; i >= 0; i--) { // reverse order!
504          DirectoryDiff diff = diffList.get(i);
505          SnapshotDiffSection.DirectoryDiff.Builder db = SnapshotDiffSection.
506              DirectoryDiff.newBuilder().setSnapshotId(diff.getSnapshotId())
507                           .setChildrenSize(diff.getChildrenSize())
508                           .setIsSnapshotRoot(diff.isSnapshotRoot());
509          INodeDirectoryAttributes copy = diff.snapshotINode;
510          if (!diff.isSnapshotRoot() && copy != null) {
511            db.setName(ByteString.copyFrom(copy.getLocalNameBytes()))
512                .setSnapshotCopy(
513                    buildINodeDirectory(copy, parent.getSaverContext()));
514          }
515          // process created list and deleted list
516          List<INode> created = diff.getChildrenDiff()
517              .getList(ListType.CREATED);
518          db.setCreatedListSize(created.size());
519          List<INode> deleted = diff.getChildrenDiff().getList(ListType.DELETED);
520          for (INode d : deleted) {
521            if (d.isReference()) {
522              refList.add(d.asReference());
523              db.addDeletedINodeRef(refList.size() - 1);
524            } else {
525              db.addDeletedINode(d.getId());
526            }
527          }
528          db.build().writeDelimitedTo(out);
529          saveCreatedList(created, out);
530        }
531      }
532    }
533  }
534
535  private FSImageFormatPBSnapshot(){}
536}