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