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.PrintStream;
021import java.io.PrintWriter;
022import java.io.StringWriter;
023import java.util.List;
024import java.util.Map;
025
026import com.google.common.collect.ImmutableMap;
027import com.google.common.collect.Maps;
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.apache.hadoop.classification.InterfaceAudience;
031import org.apache.hadoop.fs.ContentSummary;
032import org.apache.hadoop.fs.Path;
033import org.apache.hadoop.fs.permission.FsPermission;
034import org.apache.hadoop.fs.permission.PermissionStatus;
035import org.apache.hadoop.hdfs.DFSUtilClient;
036import org.apache.hadoop.hdfs.protocol.HdfsConstants;
037import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo;
038import org.apache.hadoop.hdfs.server.blockmanagement.BlockStoragePolicySuite;
039import org.apache.hadoop.hdfs.DFSUtil;
040import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
041import org.apache.hadoop.hdfs.server.namenode.INodeReference.DstReference;
042import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithName;
043import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
044import org.apache.hadoop.hdfs.util.Diff;
045import org.apache.hadoop.util.ChunkedArrayList;
046import org.apache.hadoop.util.StringUtils;
047
048import com.google.common.annotations.VisibleForTesting;
049import com.google.common.base.Preconditions;
050
051/**
052 * We keep an in-memory representation of the file/block hierarchy.
053 * This is a base INode class containing common fields for file and 
054 * directory inodes.
055 */
056@InterfaceAudience.Private
057public abstract class INode implements INodeAttributes, Diff.Element<byte[]> {
058  public static final Log LOG = LogFactory.getLog(INode.class);
059
060  /** parent is either an {@link INodeDirectory} or an {@link INodeReference}.*/
061  private INode parent = null;
062
063  INode(INode parent) {
064    this.parent = parent;
065  }
066
067  /** Get inode id */
068  public abstract long getId();
069
070  /**
071   * Check whether this is the root inode.
072   */
073  final boolean isRoot() {
074    return getLocalNameBytes().length == 0;
075  }
076
077  /** Get the {@link PermissionStatus} */
078  abstract PermissionStatus getPermissionStatus(int snapshotId);
079
080  /** The same as getPermissionStatus(null). */
081  final PermissionStatus getPermissionStatus() {
082    return getPermissionStatus(Snapshot.CURRENT_STATE_ID);
083  }
084
085  /**
086   * @param snapshotId
087   *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
088   *          from the given snapshot; otherwise, get the result from the
089   *          current inode.
090   * @return user name
091   */
092  abstract String getUserName(int snapshotId);
093
094  /** The same as getUserName(Snapshot.CURRENT_STATE_ID). */
095  @Override
096  public final String getUserName() {
097    return getUserName(Snapshot.CURRENT_STATE_ID);
098  }
099
100  /** Set user */
101  abstract void setUser(String user);
102
103  /** Set user */
104  final INode setUser(String user, int latestSnapshotId) {
105    recordModification(latestSnapshotId);
106    setUser(user);
107    return this;
108  }
109  /**
110   * @param snapshotId
111   *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
112   *          from the given snapshot; otherwise, get the result from the
113   *          current inode.
114   * @return group name
115   */
116  abstract String getGroupName(int snapshotId);
117
118  /** The same as getGroupName(Snapshot.CURRENT_STATE_ID). */
119  @Override
120  public final String getGroupName() {
121    return getGroupName(Snapshot.CURRENT_STATE_ID);
122  }
123
124  /** Set group */
125  abstract void setGroup(String group);
126
127  /** Set group */
128  final INode setGroup(String group, int latestSnapshotId) {
129    recordModification(latestSnapshotId);
130    setGroup(group);
131    return this;
132  }
133
134  /**
135   * @param snapshotId
136   *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
137   *          from the given snapshot; otherwise, get the result from the
138   *          current inode.
139   * @return permission.
140   */
141  abstract FsPermission getFsPermission(int snapshotId);
142  
143  /** The same as getFsPermission(Snapshot.CURRENT_STATE_ID). */
144  @Override
145  public final FsPermission getFsPermission() {
146    return getFsPermission(Snapshot.CURRENT_STATE_ID);
147  }
148
149  /** Set the {@link FsPermission} of this {@link INode} */
150  abstract void setPermission(FsPermission permission);
151
152  /** Set the {@link FsPermission} of this {@link INode} */
153  INode setPermission(FsPermission permission, int latestSnapshotId) {
154    recordModification(latestSnapshotId);
155    setPermission(permission);
156    return this;
157  }
158
159  abstract AclFeature getAclFeature(int snapshotId);
160
161  @Override
162  public final AclFeature getAclFeature() {
163    return getAclFeature(Snapshot.CURRENT_STATE_ID);
164  }
165
166  abstract void addAclFeature(AclFeature aclFeature);
167
168  final INode addAclFeature(AclFeature aclFeature, int latestSnapshotId) {
169    recordModification(latestSnapshotId);
170    addAclFeature(aclFeature);
171    return this;
172  }
173
174  abstract void removeAclFeature();
175
176  final INode removeAclFeature(int latestSnapshotId) {
177    recordModification(latestSnapshotId);
178    removeAclFeature();
179    return this;
180  }
181
182  /**
183   * @param snapshotId
184   *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
185   *          from the given snapshot; otherwise, get the result from the
186   *          current inode.
187   * @return XAttrFeature
188   */  
189  abstract XAttrFeature getXAttrFeature(int snapshotId);
190  
191  @Override
192  public final XAttrFeature getXAttrFeature() {
193    return getXAttrFeature(Snapshot.CURRENT_STATE_ID);
194  }
195  
196  /**
197   * Set <code>XAttrFeature</code> 
198   */
199  abstract void addXAttrFeature(XAttrFeature xAttrFeature);
200  
201  final INode addXAttrFeature(XAttrFeature xAttrFeature, int latestSnapshotId) {
202    recordModification(latestSnapshotId);
203    addXAttrFeature(xAttrFeature);
204    return this;
205  }
206  
207  /**
208   * Remove <code>XAttrFeature</code> 
209   */
210  abstract void removeXAttrFeature();
211  
212  final INode removeXAttrFeature(int lastestSnapshotId) {
213    recordModification(lastestSnapshotId);
214    removeXAttrFeature();
215    return this;
216  }
217  
218  /**
219   * @return if the given snapshot id is {@link Snapshot#CURRENT_STATE_ID},
220   *         return this; otherwise return the corresponding snapshot inode.
221   */
222  public INodeAttributes getSnapshotINode(final int snapshotId) {
223    return this;
224  }
225
226  /** Is this inode in the latest snapshot? */
227  public final boolean isInLatestSnapshot(final int latestSnapshotId) {
228    if (latestSnapshotId == Snapshot.CURRENT_STATE_ID ||
229        latestSnapshotId == Snapshot.NO_SNAPSHOT_ID) {
230      return false;
231    }
232    // if parent is a reference node, parent must be a renamed node. We can 
233    // stop the check at the reference node.
234    if (parent != null && parent.isReference()) {
235      return true;
236    }
237    final INodeDirectory parentDir = getParent();
238    if (parentDir == null) { // root
239      return true;
240    }
241    if (!parentDir.isInLatestSnapshot(latestSnapshotId)) {
242      return false;
243    }
244    final INode child = parentDir.getChild(getLocalNameBytes(), latestSnapshotId);
245    if (this == child) {
246      return true;
247    }
248    return child != null && child.isReference() &&
249        this == child.asReference().getReferredINode();
250  }
251  
252  /** @return true if the given inode is an ancestor directory of this inode. */
253  public final boolean isAncestorDirectory(final INodeDirectory dir) {
254    for(INodeDirectory p = getParent(); p != null; p = p.getParent()) {
255      if (p == dir) {
256        return true;
257      }
258    }
259    return false;
260  }
261
262  /**
263   * When {@link #recordModification} is called on a referred node,
264   * this method tells which snapshot the modification should be
265   * associated with: the snapshot that belongs to the SRC tree of the rename
266   * operation, or the snapshot belonging to the DST tree.
267   * 
268   * @param latestInDst
269   *          id of the latest snapshot in the DST tree above the reference node
270   * @return True: the modification should be recorded in the snapshot that
271   *         belongs to the SRC tree. False: the modification should be
272   *         recorded in the snapshot that belongs to the DST tree.
273   */
274  public final boolean shouldRecordInSrcSnapshot(final int latestInDst) {
275    Preconditions.checkState(!isReference());
276
277    if (latestInDst == Snapshot.CURRENT_STATE_ID) {
278      return true;
279    }
280    INodeReference withCount = getParentReference();
281    if (withCount != null) {
282      int dstSnapshotId = withCount.getParentReference().getDstSnapshotId();
283      if (dstSnapshotId != Snapshot.CURRENT_STATE_ID
284          && dstSnapshotId >= latestInDst) {
285        return true;
286      }
287    }
288    return false;
289  }
290
291  /**
292   * This inode is being modified.  The previous version of the inode needs to
293   * be recorded in the latest snapshot.
294   *
295   * @param latestSnapshotId The id of the latest snapshot that has been taken.
296   *                         Note that it is {@link Snapshot#CURRENT_STATE_ID} 
297   *                         if no snapshots have been taken.
298   */
299  abstract void recordModification(final int latestSnapshotId);
300
301  /** Check whether it's a reference. */
302  public boolean isReference() {
303    return false;
304  }
305
306  /** Cast this inode to an {@link INodeReference}.  */
307  public INodeReference asReference() {
308    throw new IllegalStateException("Current inode is not a reference: "
309        + this.toDetailString());
310  }
311
312  /**
313   * Check whether it's a file.
314   */
315  public boolean isFile() {
316    return false;
317  }
318
319  /** Cast this inode to an {@link INodeFile}.  */
320  public INodeFile asFile() {
321    throw new IllegalStateException("Current inode is not a file: "
322        + this.toDetailString());
323  }
324
325  /**
326   * Check whether it's a directory
327   */
328  public boolean isDirectory() {
329    return false;
330  }
331
332  /** Cast this inode to an {@link INodeDirectory}.  */
333  public INodeDirectory asDirectory() {
334    throw new IllegalStateException("Current inode is not a directory: "
335        + this.toDetailString());
336  }
337
338  /**
339   * Check whether it's a symlink
340   */
341  public boolean isSymlink() {
342    return false;
343  }
344
345  /** Cast this inode to an {@link INodeSymlink}.  */
346  public INodeSymlink asSymlink() {
347    throw new IllegalStateException("Current inode is not a symlink: "
348        + this.toDetailString());
349  }
350
351  /**
352   * Clean the subtree under this inode and collect the blocks from the descents
353   * for further block deletion/update. The current inode can either resides in
354   * the current tree or be stored as a snapshot copy.
355   * 
356   * <pre>
357   * In general, we have the following rules. 
358   * 1. When deleting a file/directory in the current tree, we have different 
359   * actions according to the type of the node to delete. 
360   * 
361   * 1.1 The current inode (this) is an {@link INodeFile}. 
362   * 1.1.1 If {@code prior} is null, there is no snapshot taken on ancestors 
363   * before. Thus we simply destroy (i.e., to delete completely, no need to save 
364   * snapshot copy) the current INode and collect its blocks for further 
365   * cleansing.
366   * 1.1.2 Else do nothing since the current INode will be stored as a snapshot
367   * copy.
368   * 
369   * 1.2 The current inode is an {@link INodeDirectory}.
370   * 1.2.1 If {@code prior} is null, there is no snapshot taken on ancestors 
371   * before. Similarly, we destroy the whole subtree and collect blocks.
372   * 1.2.2 Else do nothing with the current INode. Recursively clean its 
373   * children.
374   * 
375   * 1.3 The current inode is a file with snapshot.
376   * Call recordModification(..) to capture the current states.
377   * Mark the INode as deleted.
378   * 
379   * 1.4 The current inode is an {@link INodeDirectory} with snapshot feature.
380   * Call recordModification(..) to capture the current states. 
381   * Destroy files/directories created after the latest snapshot 
382   * (i.e., the inodes stored in the created list of the latest snapshot).
383   * Recursively clean remaining children. 
384   *
385   * 2. When deleting a snapshot.
386   * 2.1 To clean {@link INodeFile}: do nothing.
387   * 2.2 To clean {@link INodeDirectory}: recursively clean its children.
388   * 2.3 To clean INodeFile with snapshot: delete the corresponding snapshot in
389   * its diff list.
390   * 2.4 To clean {@link INodeDirectory} with snapshot: delete the corresponding 
391   * snapshot in its diff list. Recursively clean its children.
392   * </pre>
393   *
394   * @param reclaimContext
395   *        Record blocks and inodes that need to be reclaimed.
396   * @param snapshotId
397   *        The id of the snapshot to delete.
398   *        {@link Snapshot#CURRENT_STATE_ID} means to delete the current
399   *        file/directory.
400   * @param priorSnapshotId
401   *        The id of the latest snapshot before the to-be-deleted snapshot.
402   *        When deleting a current inode, this parameter captures the latest
403   *        snapshot.
404   */
405  public abstract void cleanSubtree(ReclaimContext reclaimContext,
406      final int snapshotId, int priorSnapshotId);
407
408  /**
409   * Destroy self and clear everything! If the INode is a file, this method
410   * collects its blocks for further block deletion. If the INode is a
411   * directory, the method goes down the subtree and collects blocks from the
412   * descents, and clears its parent/children references as well. The method
413   * also clears the diff list if the INode contains snapshot diff list.
414   *
415   * @param reclaimContext
416   *        Record blocks and inodes that need to be reclaimed.
417   */
418  public abstract void destroyAndCollectBlocks(ReclaimContext reclaimContext);
419
420  /** Compute {@link ContentSummary}. Blocking call */
421  public final ContentSummary computeContentSummary(BlockStoragePolicySuite bsps) {
422    return computeAndConvertContentSummary(Snapshot.CURRENT_STATE_ID,
423        new ContentSummaryComputationContext(bsps));
424  }
425
426  /**
427   * Compute {@link ContentSummary}. 
428   */
429  public final ContentSummary computeAndConvertContentSummary(int snapshotId,
430      ContentSummaryComputationContext summary) {
431    computeContentSummary(snapshotId, summary);
432    summary.tallyDeletedSnapshottedINodes();
433    final ContentCounts counts = summary.getCounts();
434    final ContentCounts snapshotCounts = summary.getSnapshotCounts();
435    final QuotaCounts q = getQuotaCounts();
436    return new ContentSummary.Builder().
437        length(counts.getLength()).
438        fileCount(counts.getFileCount() + counts.getSymlinkCount()).
439        directoryCount(counts.getDirectoryCount()).
440        quota(q.getNameSpace()).
441        spaceConsumed(counts.getStoragespace()).
442        spaceQuota(q.getStorageSpace()).
443        typeConsumed(counts.getTypeSpaces()).
444        typeQuota(q.getTypeSpaces().asArray()).
445        snapshotLength(snapshotCounts.getLength()).
446        snapshotFileCount(snapshotCounts.getFileCount()).
447        snapshotDirectoryCount(snapshotCounts.getDirectoryCount()).
448        snapshotSpaceConsumed(snapshotCounts.getStoragespace()).
449        build();
450  }
451
452  /**
453   * Count subtree content summary with a {@link ContentCounts}.
454   *
455   * @param snapshotId Specify the time range for the calculation. If this
456   *                   parameter equals to {@link Snapshot#CURRENT_STATE_ID},
457   *                   the result covers both the current states and all the
458   *                   snapshots. Otherwise the result only covers all the
459   *                   files/directories contained in the specific snapshot.
460   * @param summary the context object holding counts for the subtree.
461   * @return The same objects as summary.
462   */
463  public abstract ContentSummaryComputationContext computeContentSummary(
464      int snapshotId, ContentSummaryComputationContext summary);
465
466
467  /**
468   * Check and add namespace/storagespace/storagetype consumed to itself and the ancestors.
469   * @throws QuotaExceededException if quote is violated.
470   */
471  public void addSpaceConsumed(QuotaCounts counts, boolean verify)
472    throws QuotaExceededException {
473    addSpaceConsumed2Parent(counts, verify);
474  }
475
476  /**
477   * Check and add namespace/storagespace/storagetype consumed to itself and the ancestors.
478   * @throws QuotaExceededException if quote is violated.
479   */
480  void addSpaceConsumed2Parent(QuotaCounts counts, boolean verify)
481    throws QuotaExceededException {
482    if (parent != null) {
483      parent.addSpaceConsumed(counts, verify);
484    }
485  }
486
487  /**
488   * Get the quota set for this inode
489   * @return the quota counts.  The count is -1 if it is not set.
490   */
491  public QuotaCounts getQuotaCounts() {
492    return new QuotaCounts.Builder().
493        nameSpace(HdfsConstants.QUOTA_RESET).
494        storageSpace(HdfsConstants.QUOTA_RESET).
495        typeSpaces(HdfsConstants.QUOTA_RESET).
496        build();
497  }
498
499  public final boolean isQuotaSet() {
500    final QuotaCounts qc = getQuotaCounts();
501    return qc.anyNsSsCountGreaterOrEqual(0) || qc.anyTypeSpaceCountGreaterOrEqual(0);
502  }
503
504  /**
505   * Count subtree {@link Quota#NAMESPACE} and {@link Quota#STORAGESPACE} usages.
506   * Entry point for FSDirectory where blockStoragePolicyId is given its initial
507   * value.
508   */
509  public final QuotaCounts computeQuotaUsage(BlockStoragePolicySuite bsps) {
510    final byte storagePolicyId = isSymlink() ?
511        HdfsConstants.BLOCK_STORAGE_POLICY_ID_UNSPECIFIED : getStoragePolicyID();
512    return computeQuotaUsage(bsps, storagePolicyId, true,
513        Snapshot.CURRENT_STATE_ID);
514  }
515
516  /**
517   * Count subtree {@link Quota#NAMESPACE} and {@link Quota#STORAGESPACE} usages.
518   * 
519   * With the existence of {@link INodeReference}, the same inode and its
520   * subtree may be referred by multiple {@link WithName} nodes and a
521   * {@link DstReference} node. To avoid circles while quota usage computation,
522   * we have the following rules:
523   * 
524   * <pre>
525   * 1. For a {@link DstReference} node, since the node must be in the current
526   * tree (or has been deleted as the end point of a series of rename 
527   * operations), we compute the quota usage of the referred node (and its 
528   * subtree) in the regular manner, i.e., including every inode in the current
529   * tree and in snapshot copies, as well as the size of diff list.
530   * 
531   * 2. For a {@link WithName} node, since the node must be in a snapshot, we 
532   * only count the quota usage for those nodes that still existed at the 
533   * creation time of the snapshot associated with the {@link WithName} node.
534   * We do not count in the size of the diff list.  
535   * <pre>
536   *
537   * @param bsps Block storage policy suite to calculate intended storage type usage
538   * @param blockStoragePolicyId block storage policy id of the current INode
539   * @param useCache Whether to use cached quota usage. Note that 
540   *                 {@link WithName} node never uses cache for its subtree.
541   * @param lastSnapshotId {@link Snapshot#CURRENT_STATE_ID} indicates the 
542   *                       computation is in the current tree. Otherwise the id
543   *                       indicates the computation range for a 
544   *                       {@link WithName} node.
545   * @return The subtree quota counts.
546   */
547  public abstract QuotaCounts computeQuotaUsage(BlockStoragePolicySuite bsps,
548      byte blockStoragePolicyId, boolean useCache, int lastSnapshotId);
549
550  public final QuotaCounts computeQuotaUsage(BlockStoragePolicySuite bsps,
551      boolean useCache) {
552    final byte storagePolicyId = isSymlink() ?
553        HdfsConstants.BLOCK_STORAGE_POLICY_ID_UNSPECIFIED : getStoragePolicyID();
554    return computeQuotaUsage(bsps, storagePolicyId, useCache,
555        Snapshot.CURRENT_STATE_ID);
556  }
557
558  /**
559   * @return null if the local name is null; otherwise, return the local name.
560   */
561  public final String getLocalName() {
562    final byte[] name = getLocalNameBytes();
563    return name == null? null: DFSUtil.bytes2String(name);
564  }
565
566  @Override
567  public final byte[] getKey() {
568    return getLocalNameBytes();
569  }
570
571  /**
572   * Set local file name
573   */
574  public abstract void setLocalName(byte[] name);
575
576  public String getFullPathName() {
577    // Get the full path name of this inode.
578    if (isRoot()) {
579      return Path.SEPARATOR;
580    }
581    // compute size of needed bytes for the path
582    int idx = 0;
583    for (INode inode = this; inode != null; inode = inode.getParent()) {
584      // add component + delimiter (if not tail component)
585      idx += inode.getLocalNameBytes().length + (inode != this ? 1 : 0);
586    }
587    byte[] path = new byte[idx];
588    for (INode inode = this; inode != null; inode = inode.getParent()) {
589      if (inode != this) {
590        path[--idx] = Path.SEPARATOR_CHAR;
591      }
592      byte[] name = inode.getLocalNameBytes();
593      idx -= name.length;
594      System.arraycopy(name, 0, path, idx, name.length);
595    }
596    return DFSUtil.bytes2String(path);
597  }
598
599  public byte[][] getPathComponents() {
600    int n = 0;
601    for (INode inode = this; inode != null; inode = inode.getParent()) {
602      n++;
603    }
604    byte[][] components = new byte[n][];
605    for (INode inode = this; inode != null; inode = inode.getParent()) {
606      components[--n] = inode.getLocalNameBytes();
607    }
608    return components;
609  }
610
611  @Override
612  public String toString() {
613    return getLocalName();
614  }
615
616  @VisibleForTesting
617  public final String getObjectString() {
618    return getClass().getSimpleName() + "@"
619        + Integer.toHexString(super.hashCode());
620  }
621
622  /** @return a string description of the parent. */
623  @VisibleForTesting
624  public final String getParentString() {
625    final INodeReference parentRef = getParentReference();
626    if (parentRef != null) {
627      return "parentRef=" + parentRef.getLocalName() + "->";
628    } else {
629      final INodeDirectory parentDir = getParent();
630      if (parentDir != null) {
631        return "parentDir=" + parentDir.getLocalName() + "/";
632      } else {
633        return "parent=null";
634      }
635    }
636  }
637
638  @VisibleForTesting
639  public String toDetailString() {
640    return toString() + "(" + getObjectString() + "), " + getParentString();
641  }
642
643  /** @return the parent directory */
644  public final INodeDirectory getParent() {
645    return parent == null? null
646        : parent.isReference()? getParentReference().getParent(): parent.asDirectory();
647  }
648
649  /**
650   * @return the parent as a reference if this is a referred inode;
651   *         otherwise, return null.
652   */
653  public INodeReference getParentReference() {
654    return parent == null || !parent.isReference()? null: (INodeReference)parent;
655  }
656
657  /** Set parent directory */
658  public final void setParent(INodeDirectory parent) {
659    this.parent = parent;
660  }
661
662  /** Set container. */
663  public final void setParentReference(INodeReference parent) {
664    this.parent = parent;
665  }
666
667  /** Clear references to other objects. */
668  public void clear() {
669    setParent(null);
670  }
671
672  /**
673   * @param snapshotId
674   *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
675   *          from the given snapshot; otherwise, get the result from the
676   *          current inode.
677   * @return modification time.
678   */
679  abstract long getModificationTime(int snapshotId);
680
681  /** The same as getModificationTime(Snapshot.CURRENT_STATE_ID). */
682  @Override
683  public final long getModificationTime() {
684    return getModificationTime(Snapshot.CURRENT_STATE_ID);
685  }
686
687  /** Update modification time if it is larger than the current value. */
688  public abstract INode updateModificationTime(long mtime, int latestSnapshotId);
689
690  /** Set the last modification time of inode. */
691  public abstract void setModificationTime(long modificationTime);
692
693  /** Set the last modification time of inode. */
694  public final INode setModificationTime(long modificationTime,
695      int latestSnapshotId) {
696    recordModification(latestSnapshotId);
697    setModificationTime(modificationTime);
698    return this;
699  }
700
701  /**
702   * @param snapshotId
703   *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
704   *          from the given snapshot; otherwise, get the result from the
705   *          current inode.
706   * @return access time
707   */
708  abstract long getAccessTime(int snapshotId);
709
710  /** The same as getAccessTime(Snapshot.CURRENT_STATE_ID). */
711  @Override
712  public final long getAccessTime() {
713    return getAccessTime(Snapshot.CURRENT_STATE_ID);
714  }
715
716  /**
717   * Set last access time of inode.
718   */
719  public abstract void setAccessTime(long accessTime);
720
721  /**
722   * Set last access time of inode.
723   */
724  public final INode setAccessTime(long accessTime, int latestSnapshotId) {
725    recordModification(latestSnapshotId);
726    setAccessTime(accessTime);
727    return this;
728  }
729
730  /**
731   * @return the latest block storage policy id of the INode. Specifically,
732   * if a storage policy is directly specified on the INode then return the ID
733   * of that policy. Otherwise follow the latest parental path and return the
734   * ID of the first specified storage policy.
735   */
736  public abstract byte getStoragePolicyID();
737
738  /**
739   * @return the storage policy directly specified on the INode. Return
740   * {@link HdfsConstants#BLOCK_STORAGE_POLICY_ID_UNSPECIFIED} if no policy has
741   * been specified.
742   */
743  public abstract byte getLocalStoragePolicyID();
744
745  /**
746   * Get the storage policy ID while computing quota usage
747   * @param parentStoragePolicyId the storage policy ID of the parent directory
748   * @return the storage policy ID of this INode. Note that for an
749   * {@link INodeSymlink} we return {@link HdfsConstants#BLOCK_STORAGE_POLICY_ID_UNSPECIFIED}
750   * instead of throwing Exception
751   */
752  public byte getStoragePolicyIDForQuota(byte parentStoragePolicyId) {
753    byte localId = isSymlink() ?
754        HdfsConstants.BLOCK_STORAGE_POLICY_ID_UNSPECIFIED : getLocalStoragePolicyID();
755    return localId != HdfsConstants.BLOCK_STORAGE_POLICY_ID_UNSPECIFIED ?
756        localId : parentStoragePolicyId;
757  }
758
759  /**
760   * Breaks {@code path} into components.
761   * @return array of byte arrays each of which represents
762   * a single path component.
763   */
764  @VisibleForTesting
765  public static byte[][] getPathComponents(String path) {
766    checkAbsolutePath(path);
767    return DFSUtil.getPathComponents(path);
768  }
769
770  /**
771   * Splits an absolute {@code path} into an array of path components.
772   * @throws AssertionError if the given path is invalid.
773   * @return array of path components.
774   */
775  public static String[] getPathNames(String path) {
776    checkAbsolutePath(path);
777    return StringUtils.split(path, Path.SEPARATOR_CHAR);
778  }
779
780  private static void checkAbsolutePath(final String path) {
781    if (path == null || !path.startsWith(Path.SEPARATOR)) {
782      throw new AssertionError("Absolute path required, but got '"
783          + path + "'");
784    }
785  }
786
787  @Override
788  public final int compareTo(byte[] bytes) {
789    return DFSUtilClient.compareBytes(getLocalNameBytes(), bytes);
790  }
791
792  @Override
793  public final boolean equals(Object that) {
794    if (this == that) {
795      return true;
796    }
797    if (that == null || !(that instanceof INode)) {
798      return false;
799    }
800    return getId() == ((INode) that).getId();
801  }
802
803  @Override
804  public final int hashCode() {
805    long id = getId();
806    return (int)(id^(id>>>32));  
807  }
808  
809  /**
810   * Dump the subtree starting from this inode.
811   * @return a text representation of the tree.
812   */
813  @VisibleForTesting
814  public final StringBuffer dumpTreeRecursively() {
815    final StringWriter out = new StringWriter(); 
816    dumpTreeRecursively(new PrintWriter(out, true), new StringBuilder(),
817        Snapshot.CURRENT_STATE_ID);
818    return out.getBuffer();
819  }
820
821  @VisibleForTesting
822  public final void dumpTreeRecursively(PrintStream out) {
823    out.println(dumpTreeRecursively().toString());
824  }
825
826  /**
827   * Dump tree recursively.
828   * @param prefix The prefix string that each line should print.
829   */
830  @VisibleForTesting
831  public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix,
832      int snapshotId) {
833    out.print(prefix);
834    out.print(" ");
835    final String name = getLocalName();
836    out.print(name.isEmpty()? "/": name);
837    out.print("   (");
838    out.print(getObjectString());
839    out.print("), ");
840    out.print(getParentString());
841    out.print(", " + getPermissionStatus(snapshotId));
842  }
843
844  /**
845   * Information used to record quota usage delta. This data structure is
846   * usually passed along with an operation like {@link #cleanSubtree}. Note
847   * that after the operation the delta counts should be decremented from the
848   * ancestral directories' quota usage.
849   */
850  public static class QuotaDelta {
851    private final QuotaCounts counts;
852    /**
853     * The main usage of this map is to track the quota delta that should be
854     * applied to another path. This usually happens when we reclaim INodes and
855     * blocks while deleting snapshots, and hit an INodeReference. Because the
856     * quota usage for a renamed+snapshotted file/directory is counted in both
857     * the current and historical parents, any change of its quota usage may
858     * need to be propagated along its parent paths both before and after the
859     * rename.
860     */
861    private final Map<INode, QuotaCounts> updateMap;
862
863    /**
864     * When deleting a snapshot we may need to update the quota for directories
865     * with quota feature. This map is used to capture these directories and
866     * their quota usage updates.
867     */
868    private final Map<INodeDirectory, QuotaCounts> quotaDirMap;
869
870    public QuotaDelta() {
871      counts = new QuotaCounts.Builder().build();
872      updateMap = Maps.newHashMap();
873      quotaDirMap = Maps.newHashMap();
874    }
875
876    public void add(QuotaCounts update) {
877      counts.add(update);
878    }
879
880    public void addUpdatePath(INodeReference inode, QuotaCounts update) {
881      QuotaCounts c = updateMap.get(inode);
882      if (c == null) {
883        c = new QuotaCounts.Builder().build();
884        updateMap.put(inode, c);
885      }
886      c.add(update);
887    }
888
889    public void addQuotaDirUpdate(INodeDirectory dir, QuotaCounts update) {
890      Preconditions.checkState(dir.isQuotaSet());
891      QuotaCounts c = quotaDirMap.get(dir);
892      if (c == null) {
893        quotaDirMap.put(dir, update);
894      } else {
895        c.add(update);
896      }
897    }
898
899    public QuotaCounts getCountsCopy() {
900      final QuotaCounts copy = new QuotaCounts.Builder().build();
901      copy.add(counts);
902      return copy;
903    }
904
905    public void setCounts(QuotaCounts c) {
906      this.counts.setNameSpace(c.getNameSpace());
907      this.counts.setStorageSpace(c.getStorageSpace());
908      this.counts.setTypeSpaces(c.getTypeSpaces());
909    }
910
911    public long getNsDelta() {
912      long nsDelta = counts.getNameSpace();
913      for (Map.Entry<INode, QuotaCounts> entry : updateMap.entrySet()) {
914        nsDelta += entry.getValue().getNameSpace();
915      }
916      return nsDelta;
917    }
918
919    public Map<INode, QuotaCounts> getUpdateMap() {
920      return ImmutableMap.copyOf(updateMap);
921    }
922
923    public Map<INodeDirectory, QuotaCounts> getQuotaDirMap() {
924      return ImmutableMap.copyOf(quotaDirMap);
925    }
926  }
927
928  /**
929   * Context object to record blocks and inodes that need to be reclaimed
930   */
931  public static class ReclaimContext {
932    protected final BlockStoragePolicySuite bsps;
933    protected final BlocksMapUpdateInfo collectedBlocks;
934    protected final List<INode> removedINodes;
935    protected final List<Long> removedUCFiles;
936    /** Used to collect quota usage delta */
937    private final QuotaDelta quotaDelta;
938
939    /**
940     * @param bsps
941 *          block storage policy suite to calculate intended storage type
942 *          usage
943     * @param collectedBlocks
944*          blocks collected from the descents for further block
945*          deletion/update will be added to the given map.
946     * @param removedINodes
947*          INodes collected from the descents for further cleaning up of
948     * @param removedUCFiles
949     */
950    public ReclaimContext(
951        BlockStoragePolicySuite bsps, BlocksMapUpdateInfo collectedBlocks,
952        List<INode> removedINodes, List<Long> removedUCFiles) {
953      this.bsps = bsps;
954      this.collectedBlocks = collectedBlocks;
955      this.removedINodes = removedINodes;
956      this.removedUCFiles = removedUCFiles;
957      this.quotaDelta = new QuotaDelta();
958    }
959
960    public BlockStoragePolicySuite storagePolicySuite() {
961      return bsps;
962    }
963
964    public BlocksMapUpdateInfo collectedBlocks() {
965      return collectedBlocks;
966    }
967
968    public QuotaDelta quotaDelta() {
969      return quotaDelta;
970    }
971
972    /**
973     * make a copy with the same collectedBlocks, removedINodes, and
974     * removedUCFiles but a new quotaDelta.
975     */
976    public ReclaimContext getCopy() {
977      return new ReclaimContext(bsps, collectedBlocks, removedINodes,
978          removedUCFiles);
979    }
980  }
981
982  /**
983   * Information used for updating the blocksMap when deleting files.
984   */
985  public static class BlocksMapUpdateInfo {
986    /**
987     * The blocks whose replication factor need to be updated.
988     */
989    public static class UpdatedReplicationInfo {
990      /**
991       * the expected replication after the update.
992       */
993      private final short targetReplication;
994      /**
995       * The block whose replication needs to be updated.
996       */
997      private final BlockInfo block;
998
999      public UpdatedReplicationInfo(short targetReplication, BlockInfo block) {
1000        this.targetReplication = targetReplication;
1001        this.block = block;
1002      }
1003
1004      public BlockInfo block() {
1005        return block;
1006      }
1007
1008      public short targetReplication() {
1009        return targetReplication;
1010      }
1011    }
1012    /**
1013     * The list of blocks that need to be removed from blocksMap
1014     */
1015    private final List<BlockInfo> toDeleteList;
1016    /**
1017     * The list of blocks whose replication factor needs to be adjusted
1018     */
1019    private final List<UpdatedReplicationInfo> toUpdateReplicationInfo;
1020
1021    public BlocksMapUpdateInfo() {
1022      toDeleteList = new ChunkedArrayList<>();
1023      toUpdateReplicationInfo = new ChunkedArrayList<>();
1024    }
1025    
1026    /**
1027     * @return The list of blocks that need to be removed from blocksMap
1028     */
1029    public List<BlockInfo> getToDeleteList() {
1030      return toDeleteList;
1031    }
1032
1033    public List<UpdatedReplicationInfo> toUpdateReplicationInfo() {
1034      return toUpdateReplicationInfo;
1035    }
1036
1037    /**
1038     * Add a to-be-deleted block into the
1039     * {@link BlocksMapUpdateInfo#toDeleteList}
1040     * @param toDelete the to-be-deleted block
1041     */
1042    public void addDeleteBlock(BlockInfo toDelete) {
1043      assert toDelete != null : "toDelete is null";
1044      toDeleteList.add(toDelete);
1045    }
1046
1047    public void removeDeleteBlock(BlockInfo block) {
1048      assert block != null : "block is null";
1049      toDeleteList.remove(block);
1050    }
1051
1052    public void addUpdateReplicationFactor(BlockInfo block, short targetRepl) {
1053      toUpdateReplicationInfo.add(
1054          new UpdatedReplicationInfo(targetRepl, block));
1055    }
1056    /**
1057     * Clear {@link BlocksMapUpdateInfo#toDeleteList}
1058     */
1059    public void clear() {
1060      toDeleteList.clear();
1061    }
1062  }
1063
1064  /** 
1065   * INode feature such as {@link FileUnderConstructionFeature}
1066   * and {@link DirectoryWithQuotaFeature}.
1067   */
1068  public interface Feature {
1069  }
1070}