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