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;
019    
020    import java.io.PrintWriter;
021    import java.util.ArrayList;
022    import java.util.Collections;
023    import java.util.Comparator;
024    import java.util.List;
025    
026    import org.apache.hadoop.fs.permission.FsPermission;
027    import org.apache.hadoop.fs.permission.PermissionStatus;
028    import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
029    import org.apache.hadoop.hdfs.server.namenode.snapshot.FileWithSnapshot;
030    import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot;
031    import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
032    
033    import com.google.common.base.Preconditions;
034    
035    /**
036     * An anonymous reference to an inode.
037     *
038     * This class and its subclasses are used to support multiple access paths.
039     * A file/directory may have multiple access paths when it is stored in some
040     * snapshots and it is renamed/moved to other locations.
041     * 
042     * For example,
043     * (1) Support we have /abc/foo, say the inode of foo is inode(id=1000,name=foo)
044     * (2) create snapshot s0 for /abc
045     * (3) mv /abc/foo /xyz/bar, i.e. inode(id=1000,name=...) is renamed from "foo"
046     *     to "bar" and its parent becomes /xyz.
047     * 
048     * Then, /xyz/bar and /abc/.snapshot/s0/foo are two different access paths to
049     * the same inode, inode(id=1000,name=bar).
050     *
051     * With references, we have the following
052     * - /abc has a child ref(id=1001,name=foo).
053     * - /xyz has a child ref(id=1002) 
054     * - Both ref(id=1001,name=foo) and ref(id=1002) point to another reference,
055     *   ref(id=1003,count=2).
056     * - Finally, ref(id=1003,count=2) points to inode(id=1000,name=bar).
057     * 
058     * Note 1: For a reference without name, e.g. ref(id=1002), it uses the name
059     *         of the referred inode.
060     * Note 2: getParent() always returns the parent in the current state, e.g.
061     *         inode(id=1000,name=bar).getParent() returns /xyz but not /abc.
062     */
063    public abstract class INodeReference extends INode {
064      /**
065       * Try to remove the given reference and then return the reference count.
066       * If the given inode is not a reference, return -1;
067       */
068      public static int tryRemoveReference(INode inode) {
069        if (!inode.isReference()) {
070          return -1;
071        }
072        return removeReference(inode.asReference());
073      }
074    
075      /**
076       * Remove the given reference and then return the reference count.
077       * If the referred inode is not a WithCount, return -1;
078       */
079      private static int removeReference(INodeReference ref) {
080        final INode referred = ref.getReferredINode();
081        if (!(referred instanceof WithCount)) {
082          return -1;
083        }
084        
085        WithCount wc = (WithCount) referred;
086        wc.removeReference(ref);
087        return wc.getReferenceCount();
088      }
089    
090      /**
091       * When destroying a reference node (WithName or DstReference), we call this
092       * method to identify the snapshot which is the latest snapshot before the
093       * reference node's creation. 
094       */
095      static Snapshot getPriorSnapshot(INodeReference ref) {
096        WithCount wc = (WithCount) ref.getReferredINode();
097        WithName wn = null;
098        if (ref instanceof DstReference) {
099          wn = wc.getLastWithName();
100        } else if (ref instanceof WithName) {
101          wn = wc.getPriorWithName((WithName) ref);
102        }
103        if (wn != null) {
104          INode referred = wc.getReferredINode();
105          if (referred instanceof FileWithSnapshot) {
106            return ((FileWithSnapshot) referred).getDiffs().getPrior(
107                wn.lastSnapshotId);
108          } else if (referred instanceof INodeDirectoryWithSnapshot) { 
109            return ((INodeDirectoryWithSnapshot) referred).getDiffs().getPrior(
110                wn.lastSnapshotId);
111          }
112        }
113        return null;
114      }
115      
116      private INode referred;
117      
118      public INodeReference(INode parent, INode referred) {
119        super(parent);
120        this.referred = referred;
121      }
122    
123      public final INode getReferredINode() {
124        return referred;
125      }
126    
127      public final void setReferredINode(INode referred) {
128        this.referred = referred;
129      }
130      
131      @Override
132      public final boolean isReference() {
133        return true;
134      }
135      
136      @Override
137      public final INodeReference asReference() {
138        return this;
139      }
140    
141      @Override
142      public final boolean isFile() {
143        return referred.isFile();
144      }
145      
146      @Override
147      public final INodeFile asFile() {
148        return referred.asFile();
149      }
150      
151      @Override
152      public final boolean isDirectory() {
153        return referred.isDirectory();
154      }
155      
156      @Override
157      public final INodeDirectory asDirectory() {
158        return referred.asDirectory();
159      }
160      
161      @Override
162      public final boolean isSymlink() {
163        return referred.isSymlink();
164      }
165      
166      @Override
167      public final INodeSymlink asSymlink() {
168        return referred.asSymlink();
169      }
170    
171      @Override
172      public byte[] getLocalNameBytes() {
173        return referred.getLocalNameBytes();
174      }
175    
176      @Override
177      public void setLocalName(byte[] name) {
178        referred.setLocalName(name);
179      }
180    
181      @Override
182      public final long getId() {
183        return referred.getId();
184      }
185      
186      @Override
187      public final PermissionStatus getPermissionStatus(Snapshot snapshot) {
188        return referred.getPermissionStatus(snapshot);
189      }
190      
191      @Override
192      public final String getUserName(Snapshot snapshot) {
193        return referred.getUserName(snapshot);
194      }
195      
196      @Override
197      final void setUser(String user) {
198        referred.setUser(user);
199      }
200      
201      @Override
202      public final String getGroupName(Snapshot snapshot) {
203        return referred.getGroupName(snapshot);
204      }
205      
206      @Override
207      final void setGroup(String group) {
208        referred.setGroup(group);
209      }
210      
211      @Override
212      public final FsPermission getFsPermission(Snapshot snapshot) {
213        return referred.getFsPermission(snapshot);
214      }
215      @Override
216      public final short getFsPermissionShort() {
217        return referred.getFsPermissionShort();
218      }
219      
220      @Override
221      void setPermission(FsPermission permission) {
222        referred.setPermission(permission);
223      }
224    
225      @Override
226      public long getPermissionLong() {
227        return referred.getPermissionLong();
228      }
229    
230      @Override
231      public final long getModificationTime(Snapshot snapshot) {
232        return referred.getModificationTime(snapshot);
233      }
234      
235      @Override
236      public final INode updateModificationTime(long mtime, Snapshot latest,
237          INodeMap inodeMap) throws QuotaExceededException {
238        return referred.updateModificationTime(mtime, latest, inodeMap);
239      }
240      
241      @Override
242      public final void setModificationTime(long modificationTime) {
243        referred.setModificationTime(modificationTime);
244      }
245      
246      @Override
247      public final long getAccessTime(Snapshot snapshot) {
248        return referred.getAccessTime(snapshot);
249      }
250      
251      @Override
252      public final void setAccessTime(long accessTime) {
253        referred.setAccessTime(accessTime);
254      }
255    
256      @Override
257      final INode recordModification(Snapshot latest, final INodeMap inodeMap)
258          throws QuotaExceededException {
259        referred.recordModification(latest, inodeMap);
260        // reference is never replaced 
261        return this;
262      }
263    
264      @Override // used by WithCount
265      public Quota.Counts cleanSubtree(Snapshot snapshot, Snapshot prior,
266          BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes,
267          final boolean countDiffChange) throws QuotaExceededException {
268        return referred.cleanSubtree(snapshot, prior, collectedBlocks,
269            removedINodes, countDiffChange);
270      }
271    
272      @Override // used by WithCount
273      public void destroyAndCollectBlocks(
274          BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) {
275        if (removeReference(this) <= 0) {
276          referred.destroyAndCollectBlocks(collectedBlocks, removedINodes);
277        }
278      }
279    
280      @Override
281      public Content.Counts computeContentSummary(Content.Counts counts) {
282        return referred.computeContentSummary(counts);
283      }
284    
285      @Override
286      public Quota.Counts computeQuotaUsage(Quota.Counts counts, boolean useCache,
287          int lastSnapshotId) {
288        return referred.computeQuotaUsage(counts, useCache, lastSnapshotId);
289      }
290      
291      @Override
292      public final INodeAttributes getSnapshotINode(Snapshot snapshot) {
293        return referred.getSnapshotINode(snapshot);
294      }
295    
296      @Override
297      public final long getNsQuota() {
298        return referred.getNsQuota();
299      }
300    
301      @Override
302      public final long getDsQuota() {
303        return referred.getDsQuota();
304      }
305      
306      @Override
307      public final void clear() {
308        super.clear();
309        referred = null;
310      }
311    
312      @Override
313      public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix,
314          final Snapshot snapshot) {
315        super.dumpTreeRecursively(out, prefix, snapshot);
316        if (this instanceof DstReference) {
317          out.print(", dstSnapshotId=" + ((DstReference) this).dstSnapshotId);
318        }
319        if (this instanceof WithCount) {
320          out.print(", count=" + ((WithCount)this).getReferenceCount());
321        }
322        out.println();
323        
324        final StringBuilder b = new StringBuilder();
325        for(int i = 0; i < prefix.length(); i++) {
326          b.append(' ');
327        }
328        b.append("->");
329        getReferredINode().dumpTreeRecursively(out, b, snapshot);
330      }
331      
332      public int getDstSnapshotId() {
333        return Snapshot.INVALID_ID;
334      }
335      
336      /** An anonymous reference with reference count. */
337      public static class WithCount extends INodeReference {
338        
339        private final List<WithName> withNameList = new ArrayList<WithName>();
340        
341        /**
342         * Compare snapshot with IDs, where null indicates the current status thus
343         * is greater than any non-null snapshot.
344         */
345        public static final Comparator<WithName> WITHNAME_COMPARATOR
346            = new Comparator<WithName>() {
347          @Override
348          public int compare(WithName left, WithName right) {
349            return left.lastSnapshotId - right.lastSnapshotId;
350          }
351        };
352        
353        public WithCount(INodeReference parent, INode referred) {
354          super(parent, referred);
355          Preconditions.checkArgument(!referred.isReference());
356          referred.setParentReference(this);
357        }
358        
359        public int getReferenceCount() {
360          int count = withNameList.size();
361          if (getParentReference() != null) {
362            count++;
363          }
364          return count;
365        }
366    
367        /** Increment and then return the reference count. */
368        public void addReference(INodeReference ref) {
369          if (ref instanceof WithName) {
370            WithName refWithName = (WithName) ref;
371            int i = Collections.binarySearch(withNameList, refWithName,
372                WITHNAME_COMPARATOR);
373            Preconditions.checkState(i < 0);
374            withNameList.add(-i - 1, refWithName);
375          } else if (ref instanceof DstReference) {
376            setParentReference(ref);
377          }
378        }
379    
380        /** Decrement and then return the reference count. */
381        public void removeReference(INodeReference ref) {
382          if (ref instanceof WithName) {
383            int i = Collections.binarySearch(withNameList, (WithName) ref,
384                WITHNAME_COMPARATOR);
385            if (i >= 0) {
386              withNameList.remove(i);
387            }
388          } else if (ref == getParentReference()) {
389            setParent(null);
390          }
391        }
392        
393        WithName getLastWithName() {
394          return withNameList.size() > 0 ? 
395              withNameList.get(withNameList.size() - 1) : null;
396        }
397        
398        WithName getPriorWithName(WithName post) {
399          int i = Collections.binarySearch(withNameList, post, WITHNAME_COMPARATOR);
400          if (i > 0) {
401            return withNameList.get(i - 1);
402          } else if (i == 0 || i == -1) {
403            return null;
404          } else {
405            return withNameList.get(-i - 2);
406          }
407        }
408      }
409      
410      /** A reference with a fixed name. */
411      public static class WithName extends INodeReference {
412    
413        private final byte[] name;
414    
415        /**
416         * The id of the last snapshot in the src tree when this WithName node was 
417         * generated. When calculating the quota usage of the referred node, only 
418         * the files/dirs existing when this snapshot was taken will be counted for 
419         * this WithName node and propagated along its ancestor path.
420         */
421        private final int lastSnapshotId;
422        
423        public WithName(INodeDirectory parent, WithCount referred, byte[] name,
424            int lastSnapshotId) {
425          super(parent, referred);
426          this.name = name;
427          this.lastSnapshotId = lastSnapshotId;
428          referred.addReference(this);
429        }
430    
431        @Override
432        public final byte[] getLocalNameBytes() {
433          return name;
434        }
435    
436        @Override
437        public final void setLocalName(byte[] name) {
438          throw new UnsupportedOperationException("Cannot set name: " + getClass()
439              + " is immutable.");
440        }
441        
442        public int getLastSnapshotId() {
443          return lastSnapshotId;
444        }
445        
446        @Override
447        public final Content.Counts computeContentSummary(Content.Counts counts) {
448          //only count diskspace for WithName
449          final Quota.Counts q = Quota.Counts.newInstance();
450          computeQuotaUsage(q, false, lastSnapshotId);
451          counts.add(Content.DISKSPACE, q.get(Quota.DISKSPACE));
452          return counts;
453        }
454    
455        @Override
456        public final Quota.Counts computeQuotaUsage(Quota.Counts counts,
457            boolean useCache, int lastSnapshotId) {
458          // if this.lastSnapshotId < lastSnapshotId, the rename of the referred 
459          // node happened before the rename of its ancestor. This should be 
460          // impossible since for WithName node we only count its children at the 
461          // time of the rename. 
462          Preconditions.checkState(this.lastSnapshotId >= lastSnapshotId);
463          final INode referred = this.getReferredINode().asReference()
464              .getReferredINode();
465          // We will continue the quota usage computation using the same snapshot id
466          // as time line (if the given snapshot id is valid). Also, we cannot use 
467          // cache for the referred node since its cached quota may have already 
468          // been updated by changes in the current tree.
469          int id = lastSnapshotId > Snapshot.INVALID_ID ? 
470              lastSnapshotId : this.lastSnapshotId;
471          return referred.computeQuotaUsage(counts, false, id);
472        }
473        
474        @Override
475        public Quota.Counts cleanSubtree(final Snapshot snapshot, Snapshot prior,
476            final BlocksMapUpdateInfo collectedBlocks,
477            final List<INode> removedINodes, final boolean countDiffChange)
478            throws QuotaExceededException {
479          // since WithName node resides in deleted list acting as a snapshot copy,
480          // the parameter snapshot must be non-null
481          Preconditions.checkArgument(snapshot != null);
482          // if prior is null, we need to check snapshot belonging to the previous
483          // WithName instance
484          if (prior == null) {
485            prior = getPriorSnapshot(this);
486          }
487          
488          if (prior != null
489              && Snapshot.ID_COMPARATOR.compare(snapshot, prior) <= 0) {
490            return Quota.Counts.newInstance();
491          }
492    
493          Quota.Counts counts = getReferredINode().cleanSubtree(snapshot, prior,
494              collectedBlocks, removedINodes, false);
495          INodeReference ref = getReferredINode().getParentReference();
496          if (ref != null) {
497            ref.addSpaceConsumed(-counts.get(Quota.NAMESPACE),
498                -counts.get(Quota.DISKSPACE), true);
499          }
500          
501          if (snapshot.getId() < lastSnapshotId) {
502            // for a WithName node, when we compute its quota usage, we only count
503            // in all the nodes existing at the time of the corresponding rename op.
504            // Thus if we are deleting a snapshot before/at the snapshot associated 
505            // with lastSnapshotId, we do not need to update the quota upwards.
506            counts = Quota.Counts.newInstance();
507          }
508          return counts;
509        }
510        
511        @Override
512        public void destroyAndCollectBlocks(BlocksMapUpdateInfo collectedBlocks,
513            final List<INode> removedINodes) {
514          Snapshot snapshot = getSelfSnapshot();
515          if (removeReference(this) <= 0) {
516            getReferredINode().destroyAndCollectBlocks(collectedBlocks,
517                removedINodes);
518          } else {
519            Snapshot prior = getPriorSnapshot(this);
520            INode referred = getReferredINode().asReference().getReferredINode();
521            
522            if (snapshot != null) {
523              if (prior != null && snapshot.getId() <= prior.getId()) {
524                // the snapshot to be deleted has been deleted while traversing 
525                // the src tree of the previous rename operation. This usually 
526                // happens when rename's src and dst are under the same 
527                // snapshottable directory. E.g., the following operation sequence:
528                // 1. create snapshot s1 on /test
529                // 2. rename /test/foo/bar to /test/foo2/bar
530                // 3. create snapshot s2 on /test
531                // 4. rename foo2 again
532                // 5. delete snapshot s2
533                return;
534              }
535              try {
536                Quota.Counts counts = referred.cleanSubtree(snapshot, prior,
537                    collectedBlocks, removedINodes, false);
538                INodeReference ref = getReferredINode().getParentReference();
539                if (ref != null) {
540                  ref.addSpaceConsumed(-counts.get(Quota.NAMESPACE),
541                      -counts.get(Quota.DISKSPACE), true);
542                }
543              } catch (QuotaExceededException e) {
544                LOG.error("should not exceed quota while snapshot deletion", e);
545              }
546            }
547          }
548        }
549        
550        private Snapshot getSelfSnapshot() {
551          INode referred = getReferredINode().asReference().getReferredINode();
552          Snapshot snapshot = null;
553          if (referred instanceof FileWithSnapshot) {
554            snapshot = ((FileWithSnapshot) referred).getDiffs().getPrior(
555                lastSnapshotId);
556          } else if (referred instanceof INodeDirectoryWithSnapshot) {
557            snapshot = ((INodeDirectoryWithSnapshot) referred).getDiffs().getPrior(
558                lastSnapshotId);
559          }
560          return snapshot;
561        }
562      }
563      
564      public static class DstReference extends INodeReference {
565        /**
566         * Record the latest snapshot of the dst subtree before the rename. For
567         * later operations on the moved/renamed files/directories, if the latest
568         * snapshot is after this dstSnapshot, changes will be recorded to the
569         * latest snapshot. Otherwise changes will be recorded to the snapshot
570         * belonging to the src of the rename.
571         * 
572         * {@link Snapshot#INVALID_ID} means no dstSnapshot (e.g., src of the
573         * first-time rename).
574         */
575        private final int dstSnapshotId;
576        
577        @Override
578        public final int getDstSnapshotId() {
579          return dstSnapshotId;
580        }
581        
582        public DstReference(INodeDirectory parent, WithCount referred,
583            final int dstSnapshotId) {
584          super(parent, referred);
585          this.dstSnapshotId = dstSnapshotId;
586          referred.addReference(this);
587        }
588        
589        @Override
590        public Quota.Counts cleanSubtree(Snapshot snapshot, Snapshot prior,
591            BlocksMapUpdateInfo collectedBlocks, List<INode> removedINodes,
592            final boolean countDiffChange) throws QuotaExceededException {
593          if (snapshot == null && prior == null) {
594            Quota.Counts counts = Quota.Counts.newInstance();
595            this.computeQuotaUsage(counts, true);
596            destroyAndCollectBlocks(collectedBlocks, removedINodes);
597            return counts;
598          } else {
599            // if prior is null, we need to check snapshot belonging to the previous
600            // WithName instance
601            if (prior == null) {
602              prior = getPriorSnapshot(this);
603            }
604            // if prior is not null, and prior is not before the to-be-deleted 
605            // snapshot, we can quit here and leave the snapshot deletion work to 
606            // the src tree of rename
607            if (snapshot != null && prior != null
608                && Snapshot.ID_COMPARATOR.compare(snapshot, prior) <= 0) {
609              return Quota.Counts.newInstance();
610            }
611            return getReferredINode().cleanSubtree(snapshot, prior,
612                collectedBlocks, removedINodes, countDiffChange);
613          }
614        }
615        
616        /**
617         * {@inheritDoc}
618         * <br/>
619         * To destroy a DstReference node, we first remove its link with the 
620         * referred node. If the reference number of the referred node is <= 0, we 
621         * destroy the subtree of the referred node. Otherwise, we clean the 
622         * referred node's subtree and delete everything created after the last 
623         * rename operation, i.e., everything outside of the scope of the prior 
624         * WithName nodes.
625         */
626        @Override
627        public void destroyAndCollectBlocks(
628            BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) {
629          if (removeReference(this) <= 0) {
630            getReferredINode().destroyAndCollectBlocks(collectedBlocks,
631                removedINodes);
632          } else {
633            // we will clean everything, including files, directories, and 
634            // snapshots, that were created after this prior snapshot
635            Snapshot prior = getPriorSnapshot(this);
636            // prior must be non-null, otherwise we do not have any previous 
637            // WithName nodes, and the reference number will be 0.
638            Preconditions.checkState(prior != null);
639            // identify the snapshot created after prior
640            Snapshot snapshot = getSelfSnapshot(prior);
641            
642            INode referred = getReferredINode().asReference().getReferredINode();
643            if (referred instanceof FileWithSnapshot) {
644              // if referred is a file, it must be a FileWithSnapshot since we did
645              // recordModification before the rename
646              FileWithSnapshot sfile = (FileWithSnapshot) referred;
647              // make sure we mark the file as deleted
648              sfile.deleteCurrentFile();
649              if (snapshot != null) {
650                try {
651                  // when calling cleanSubtree of the referred node, since we 
652                  // compute quota usage updates before calling this destroy 
653                  // function, we use true for countDiffChange
654                  referred.cleanSubtree(snapshot, prior, collectedBlocks,
655                      removedINodes, true);
656                } catch (QuotaExceededException e) {
657                  LOG.error("should not exceed quota while snapshot deletion", e);
658                }
659              }
660            } else if (referred instanceof INodeDirectoryWithSnapshot) {
661              // similarly, if referred is a directory, it must be an
662              // INodeDirectoryWithSnapshot
663              INodeDirectoryWithSnapshot sdir = 
664                  (INodeDirectoryWithSnapshot) referred;
665              try {
666                INodeDirectoryWithSnapshot.destroyDstSubtree(sdir, snapshot, prior,
667                    collectedBlocks, removedINodes);
668              } catch (QuotaExceededException e) {
669                LOG.error("should not exceed quota while snapshot deletion", e);
670              }
671            }
672          }
673        }
674        
675        private Snapshot getSelfSnapshot(final Snapshot prior) {
676          WithCount wc = (WithCount) getReferredINode().asReference();
677          INode referred = wc.getReferredINode();
678          Snapshot lastSnapshot = null;
679          if (referred instanceof FileWithSnapshot) {
680            lastSnapshot = ((FileWithSnapshot) referred).getDiffs()
681                .getLastSnapshot(); 
682          } else if (referred instanceof INodeDirectoryWithSnapshot) {
683            lastSnapshot = ((INodeDirectoryWithSnapshot) referred)
684                .getLastSnapshot();
685          }
686          if (lastSnapshot != null && !lastSnapshot.equals(prior)) {
687            return lastSnapshot;
688          } else {
689            return null;
690          }
691        }
692      }
693    }