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