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.util.Diff.ListType;
069    
070    import com.google.common.base.Preconditions;
071    import com.google.protobuf.ByteString;
072    
073    @InterfaceAudience.Private
074    public 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    }