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     */
018    package org.apache.hadoop.hdfs.server.namenode.snapshot;
019    
020    import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.loadINodeDirectory;
021    import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.loadPermission;
022    import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.updateBlocksMap;
023    import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Saver.buildINodeDirectory;
024    import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Saver.buildINodeFile;
025    
026    import java.io.IOException;
027    import java.io.InputStream;
028    import java.io.OutputStream;
029    import java.util.ArrayList;
030    import java.util.Collections;
031    import java.util.Comparator;
032    import java.util.HashMap;
033    import java.util.Iterator;
034    import java.util.List;
035    import java.util.Map;
036    
037    import org.apache.hadoop.classification.InterfaceAudience;
038    import org.apache.hadoop.fs.permission.PermissionStatus;
039    import org.apache.hadoop.hdfs.server.namenode.AclFeature;
040    import org.apache.hadoop.hdfs.server.namenode.FSDirectory;
041    import org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode;
042    import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf;
043    import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.LoaderContext;
044    import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.SectionName;
045    import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
046    import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary;
047    import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeReferenceSection;
048    import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection;
049    import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection;
050    import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection.CreatedListEntry;
051    import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection.DiffEntry.Type;
052    import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotSection;
053    import org.apache.hadoop.hdfs.server.namenode.INode;
054    import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
055    import org.apache.hadoop.hdfs.server.namenode.INodeDirectoryAttributes;
056    import org.apache.hadoop.hdfs.server.namenode.INodeFile;
057    import org.apache.hadoop.hdfs.server.namenode.INodeFileAttributes;
058    import org.apache.hadoop.hdfs.server.namenode.INodeMap;
059    import org.apache.hadoop.hdfs.server.namenode.INodeReference;
060    import org.apache.hadoop.hdfs.server.namenode.INodeReference.DstReference;
061    import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount;
062    import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithName;
063    import org.apache.hadoop.hdfs.server.namenode.INodeWithAdditionalFields;
064    import org.apache.hadoop.hdfs.server.namenode.SaveNamespaceContext;
065    import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiff;
066    import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiffList;
067    import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot.Root;
068    import org.apache.hadoop.hdfs.server.namenode.XAttrFeature;
069    import org.apache.hadoop.hdfs.util.Diff.ListType;
070    
071    import com.google.common.base.Preconditions;
072    import com.google.protobuf.ByteString;
073    
074    @InterfaceAudience.Private
075    public 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    }