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}