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;
019
020import java.io.PrintWriter;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.Comparator;
024import java.util.List;
025
026import org.apache.hadoop.fs.permission.FsPermission;
027import org.apache.hadoop.fs.permission.PermissionStatus;
028import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
029import org.apache.hadoop.hdfs.server.namenode.snapshot.FileWithSnapshot;
030import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot;
031import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
032
033import 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 */
063public 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 ContentSummaryComputationContext computeContentSummary(
282      ContentSummaryComputationContext summary) {
283    return referred.computeContentSummary(summary);
284  }
285
286  @Override
287  public Quota.Counts computeQuotaUsage(Quota.Counts counts, boolean useCache,
288      int lastSnapshotId) {
289    return referred.computeQuotaUsage(counts, useCache, lastSnapshotId);
290  }
291  
292  @Override
293  public final INodeAttributes getSnapshotINode(Snapshot snapshot) {
294    return referred.getSnapshotINode(snapshot);
295  }
296
297  @Override
298  public final long getNsQuota() {
299    return referred.getNsQuota();
300  }
301
302  @Override
303  public final long getDsQuota() {
304    return referred.getDsQuota();
305  }
306  
307  @Override
308  public final void clear() {
309    super.clear();
310    referred = null;
311  }
312
313  @Override
314  public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix,
315      final Snapshot snapshot) {
316    super.dumpTreeRecursively(out, prefix, snapshot);
317    if (this instanceof DstReference) {
318      out.print(", dstSnapshotId=" + ((DstReference) this).dstSnapshotId);
319    }
320    if (this instanceof WithCount) {
321      out.print(", count=" + ((WithCount)this).getReferenceCount());
322    }
323    out.println();
324    
325    final StringBuilder b = new StringBuilder();
326    for(int i = 0; i < prefix.length(); i++) {
327      b.append(' ');
328    }
329    b.append("->");
330    getReferredINode().dumpTreeRecursively(out, b, snapshot);
331  }
332  
333  public int getDstSnapshotId() {
334    return Snapshot.INVALID_ID;
335  }
336  
337  /** An anonymous reference with reference count. */
338  public static class WithCount extends INodeReference {
339    
340    private final List<WithName> withNameList = new ArrayList<WithName>();
341    
342    /**
343     * Compare snapshot with IDs, where null indicates the current status thus
344     * is greater than any non-null snapshot.
345     */
346    public static final Comparator<WithName> WITHNAME_COMPARATOR
347        = new Comparator<WithName>() {
348      @Override
349      public int compare(WithName left, WithName right) {
350        return left.lastSnapshotId - right.lastSnapshotId;
351      }
352    };
353    
354    public WithCount(INodeReference parent, INode referred) {
355      super(parent, referred);
356      Preconditions.checkArgument(!referred.isReference());
357      referred.setParentReference(this);
358    }
359    
360    public int getReferenceCount() {
361      int count = withNameList.size();
362      if (getParentReference() != null) {
363        count++;
364      }
365      return count;
366    }
367
368    /** Increment and then return the reference count. */
369    public void addReference(INodeReference ref) {
370      if (ref instanceof WithName) {
371        WithName refWithName = (WithName) ref;
372        int i = Collections.binarySearch(withNameList, refWithName,
373            WITHNAME_COMPARATOR);
374        Preconditions.checkState(i < 0);
375        withNameList.add(-i - 1, refWithName);
376      } else if (ref instanceof DstReference) {
377        setParentReference(ref);
378      }
379    }
380
381    /** Decrement and then return the reference count. */
382    public void removeReference(INodeReference ref) {
383      if (ref instanceof WithName) {
384        int i = Collections.binarySearch(withNameList, (WithName) ref,
385            WITHNAME_COMPARATOR);
386        if (i >= 0) {
387          withNameList.remove(i);
388        }
389      } else if (ref == getParentReference()) {
390        setParent(null);
391      }
392    }
393    
394    WithName getLastWithName() {
395      return withNameList.size() > 0 ? 
396          withNameList.get(withNameList.size() - 1) : null;
397    }
398    
399    WithName getPriorWithName(WithName post) {
400      int i = Collections.binarySearch(withNameList, post, WITHNAME_COMPARATOR);
401      if (i > 0) {
402        return withNameList.get(i - 1);
403      } else if (i == 0 || i == -1) {
404        return null;
405      } else {
406        return withNameList.get(-i - 2);
407      }
408    }
409  }
410  
411  /** A reference with a fixed name. */
412  public static class WithName extends INodeReference {
413
414    private final byte[] name;
415
416    /**
417     * The id of the last snapshot in the src tree when this WithName node was 
418     * generated. When calculating the quota usage of the referred node, only 
419     * the files/dirs existing when this snapshot was taken will be counted for 
420     * this WithName node and propagated along its ancestor path.
421     */
422    private final int lastSnapshotId;
423    
424    public WithName(INodeDirectory parent, WithCount referred, byte[] name,
425        int lastSnapshotId) {
426      super(parent, referred);
427      this.name = name;
428      this.lastSnapshotId = lastSnapshotId;
429      referred.addReference(this);
430    }
431
432    @Override
433    public final byte[] getLocalNameBytes() {
434      return name;
435    }
436
437    @Override
438    public final void setLocalName(byte[] name) {
439      throw new UnsupportedOperationException("Cannot set name: " + getClass()
440          + " is immutable.");
441    }
442    
443    public int getLastSnapshotId() {
444      return lastSnapshotId;
445    }
446    
447    @Override
448    public final ContentSummaryComputationContext computeContentSummary(
449        ContentSummaryComputationContext summary) {
450      //only count diskspace for WithName
451      final Quota.Counts q = Quota.Counts.newInstance();
452      computeQuotaUsage(q, false, lastSnapshotId);
453      summary.getCounts().add(Content.DISKSPACE, q.get(Quota.DISKSPACE));
454      return summary;
455    }
456
457    @Override
458    public final Quota.Counts computeQuotaUsage(Quota.Counts counts,
459        boolean useCache, int lastSnapshotId) {
460      // if this.lastSnapshotId < lastSnapshotId, the rename of the referred 
461      // node happened before the rename of its ancestor. This should be 
462      // impossible since for WithName node we only count its children at the 
463      // time of the rename. 
464      Preconditions.checkState(this.lastSnapshotId >= lastSnapshotId);
465      final INode referred = this.getReferredINode().asReference()
466          .getReferredINode();
467      // We will continue the quota usage computation using the same snapshot id
468      // as time line (if the given snapshot id is valid). Also, we cannot use 
469      // cache for the referred node since its cached quota may have already 
470      // been updated by changes in the current tree.
471      int id = lastSnapshotId > Snapshot.INVALID_ID ? 
472          lastSnapshotId : this.lastSnapshotId;
473      return referred.computeQuotaUsage(counts, false, id);
474    }
475    
476    @Override
477    public Quota.Counts cleanSubtree(final Snapshot snapshot, Snapshot prior,
478        final BlocksMapUpdateInfo collectedBlocks,
479        final List<INode> removedINodes, final boolean countDiffChange)
480        throws QuotaExceededException {
481      // since WithName node resides in deleted list acting as a snapshot copy,
482      // the parameter snapshot must be non-null
483      Preconditions.checkArgument(snapshot != null);
484      // if prior is null, we need to check snapshot belonging to the previous
485      // WithName instance
486      if (prior == null) {
487        prior = getPriorSnapshot(this);
488      }
489      
490      if (prior != null
491          && Snapshot.ID_COMPARATOR.compare(snapshot, prior) <= 0) {
492        return Quota.Counts.newInstance();
493      }
494
495      Quota.Counts counts = getReferredINode().cleanSubtree(snapshot, prior,
496          collectedBlocks, removedINodes, false);
497      INodeReference ref = getReferredINode().getParentReference();
498      if (ref != null) {
499        ref.addSpaceConsumed(-counts.get(Quota.NAMESPACE),
500            -counts.get(Quota.DISKSPACE), true);
501      }
502      
503      if (snapshot.getId() < lastSnapshotId) {
504        // for a WithName node, when we compute its quota usage, we only count
505        // in all the nodes existing at the time of the corresponding rename op.
506        // Thus if we are deleting a snapshot before/at the snapshot associated 
507        // with lastSnapshotId, we do not need to update the quota upwards.
508        counts = Quota.Counts.newInstance();
509      }
510      return counts;
511    }
512    
513    @Override
514    public void destroyAndCollectBlocks(BlocksMapUpdateInfo collectedBlocks,
515        final List<INode> removedINodes) {
516      Snapshot snapshot = getSelfSnapshot();
517      if (removeReference(this) <= 0) {
518        getReferredINode().destroyAndCollectBlocks(collectedBlocks,
519            removedINodes);
520      } else {
521        Snapshot prior = getPriorSnapshot(this);
522        INode referred = getReferredINode().asReference().getReferredINode();
523        
524        if (snapshot != null) {
525          if (prior != null && snapshot.getId() <= prior.getId()) {
526            // the snapshot to be deleted has been deleted while traversing 
527            // the src tree of the previous rename operation. This usually 
528            // happens when rename's src and dst are under the same 
529            // snapshottable directory. E.g., the following operation sequence:
530            // 1. create snapshot s1 on /test
531            // 2. rename /test/foo/bar to /test/foo2/bar
532            // 3. create snapshot s2 on /test
533            // 4. rename foo2 again
534            // 5. delete snapshot s2
535            return;
536          }
537          try {
538            Quota.Counts counts = referred.cleanSubtree(snapshot, prior,
539                collectedBlocks, removedINodes, false);
540            INodeReference ref = getReferredINode().getParentReference();
541            if (ref != null) {
542              ref.addSpaceConsumed(-counts.get(Quota.NAMESPACE),
543                  -counts.get(Quota.DISKSPACE), true);
544            }
545          } catch (QuotaExceededException e) {
546            LOG.error("should not exceed quota while snapshot deletion", e);
547          }
548        }
549      }
550    }
551    
552    private Snapshot getSelfSnapshot() {
553      INode referred = getReferredINode().asReference().getReferredINode();
554      Snapshot snapshot = null;
555      if (referred instanceof FileWithSnapshot) {
556        snapshot = ((FileWithSnapshot) referred).getDiffs().getPrior(
557            lastSnapshotId);
558      } else if (referred instanceof INodeDirectoryWithSnapshot) {
559        snapshot = ((INodeDirectoryWithSnapshot) referred).getDiffs().getPrior(
560            lastSnapshotId);
561      }
562      return snapshot;
563    }
564  }
565  
566  public static class DstReference extends INodeReference {
567    /**
568     * Record the latest snapshot of the dst subtree before the rename. For
569     * later operations on the moved/renamed files/directories, if the latest
570     * snapshot is after this dstSnapshot, changes will be recorded to the
571     * latest snapshot. Otherwise changes will be recorded to the snapshot
572     * belonging to the src of the rename.
573     * 
574     * {@link Snapshot#INVALID_ID} means no dstSnapshot (e.g., src of the
575     * first-time rename).
576     */
577    private final int dstSnapshotId;
578    
579    @Override
580    public final int getDstSnapshotId() {
581      return dstSnapshotId;
582    }
583    
584    public DstReference(INodeDirectory parent, WithCount referred,
585        final int dstSnapshotId) {
586      super(parent, referred);
587      this.dstSnapshotId = dstSnapshotId;
588      referred.addReference(this);
589    }
590    
591    @Override
592    public Quota.Counts cleanSubtree(Snapshot snapshot, Snapshot prior,
593        BlocksMapUpdateInfo collectedBlocks, List<INode> removedINodes,
594        final boolean countDiffChange) throws QuotaExceededException {
595      if (snapshot == null && prior == null) {
596        Quota.Counts counts = Quota.Counts.newInstance();
597        this.computeQuotaUsage(counts, true);
598        destroyAndCollectBlocks(collectedBlocks, removedINodes);
599        return counts;
600      } else {
601        // if prior is null, we need to check snapshot belonging to the previous
602        // WithName instance
603        if (prior == null) {
604          prior = getPriorSnapshot(this);
605        }
606        // if prior is not null, and prior is not before the to-be-deleted 
607        // snapshot, we can quit here and leave the snapshot deletion work to 
608        // the src tree of rename
609        if (snapshot != null && prior != null
610            && Snapshot.ID_COMPARATOR.compare(snapshot, prior) <= 0) {
611          return Quota.Counts.newInstance();
612        }
613        return getReferredINode().cleanSubtree(snapshot, prior,
614            collectedBlocks, removedINodes, countDiffChange);
615      }
616    }
617    
618    /**
619     * {@inheritDoc}
620     * <br/>
621     * To destroy a DstReference node, we first remove its link with the 
622     * referred node. If the reference number of the referred node is <= 0, we 
623     * destroy the subtree of the referred node. Otherwise, we clean the 
624     * referred node's subtree and delete everything created after the last 
625     * rename operation, i.e., everything outside of the scope of the prior 
626     * WithName nodes.
627     */
628    @Override
629    public void destroyAndCollectBlocks(
630        BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) {
631      if (removeReference(this) <= 0) {
632        getReferredINode().destroyAndCollectBlocks(collectedBlocks,
633            removedINodes);
634      } else {
635        // we will clean everything, including files, directories, and 
636        // snapshots, that were created after this prior snapshot
637        Snapshot prior = getPriorSnapshot(this);
638        // prior must be non-null, otherwise we do not have any previous 
639        // WithName nodes, and the reference number will be 0.
640        Preconditions.checkState(prior != null);
641        // identify the snapshot created after prior
642        Snapshot snapshot = getSelfSnapshot(prior);
643        
644        INode referred = getReferredINode().asReference().getReferredINode();
645        if (referred instanceof FileWithSnapshot) {
646          // if referred is a file, it must be a FileWithSnapshot since we did
647          // recordModification before the rename
648          FileWithSnapshot sfile = (FileWithSnapshot) referred;
649          // make sure we mark the file as deleted
650          sfile.deleteCurrentFile();
651          try {
652            // when calling cleanSubtree of the referred node, since we 
653            // compute quota usage updates before calling this destroy 
654            // function, we use true for countDiffChange
655            referred.cleanSubtree(snapshot, prior, collectedBlocks,
656                removedINodes, true);
657          } catch (QuotaExceededException e) {
658            LOG.error("should not exceed quota while snapshot deletion", e);
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}