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.snapshot; 019 020import java.io.DataOutput; 021import java.io.IOException; 022import java.util.ArrayDeque; 023import java.util.Deque; 024import java.util.HashMap; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Map; 028 029import org.apache.hadoop.classification.InterfaceAudience; 030import org.apache.hadoop.hdfs.protocol.QuotaExceededException; 031import org.apache.hadoop.hdfs.server.namenode.Content; 032import org.apache.hadoop.hdfs.server.namenode.ContentSummaryComputationContext; 033import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization; 034import org.apache.hadoop.hdfs.server.namenode.INode; 035import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo; 036import org.apache.hadoop.hdfs.server.namenode.INodeDirectory; 037import org.apache.hadoop.hdfs.server.namenode.INodeDirectoryAttributes; 038import org.apache.hadoop.hdfs.server.namenode.INodeFile; 039import org.apache.hadoop.hdfs.server.namenode.INodeReference; 040import org.apache.hadoop.hdfs.server.namenode.Quota; 041import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotFSImageFormat.ReferenceMap; 042import org.apache.hadoop.hdfs.util.Diff; 043import org.apache.hadoop.hdfs.util.Diff.Container; 044import org.apache.hadoop.hdfs.util.Diff.ListType; 045import org.apache.hadoop.hdfs.util.Diff.UndoInfo; 046import org.apache.hadoop.hdfs.util.ReadOnlyList; 047 048import com.google.common.base.Preconditions; 049 050/** 051 * Feature used to store and process the snapshot diff information for a 052 * directory. In particular, it contains a directory diff list recording changes 053 * made to the directory and its children for each snapshot. 054 */ 055@InterfaceAudience.Private 056public class DirectoryWithSnapshotFeature implements INode.Feature { 057 /** 058 * The difference between the current state and a previous snapshot 059 * of the children list of an INodeDirectory. 060 */ 061 static class ChildrenDiff extends Diff<byte[], INode> { 062 ChildrenDiff() {} 063 064 private ChildrenDiff(final List<INode> created, final List<INode> deleted) { 065 super(created, deleted); 066 } 067 068 /** 069 * Replace the given child from the created/deleted list. 070 * @return true if the child is replaced; false if the child is not found. 071 */ 072 private final boolean replace(final ListType type, 073 final INode oldChild, final INode newChild) { 074 final List<INode> list = getList(type); 075 final int i = search(list, oldChild.getLocalNameBytes()); 076 if (i < 0 || list.get(i).getId() != oldChild.getId()) { 077 return false; 078 } 079 080 final INode removed = list.set(i, newChild); 081 Preconditions.checkState(removed == oldChild); 082 return true; 083 } 084 085 private final boolean removeChild(ListType type, final INode child) { 086 final List<INode> list = getList(type); 087 final int i = searchIndex(type, child.getLocalNameBytes()); 088 if (i >= 0 && list.get(i) == child) { 089 list.remove(i); 090 return true; 091 } 092 return false; 093 } 094 095 /** clear the created list */ 096 private Quota.Counts destroyCreatedList(final INodeDirectory currentINode, 097 final BlocksMapUpdateInfo collectedBlocks, 098 final List<INode> removedINodes) { 099 Quota.Counts counts = Quota.Counts.newInstance(); 100 final List<INode> createdList = getList(ListType.CREATED); 101 for (INode c : createdList) { 102 c.computeQuotaUsage(counts, true); 103 c.destroyAndCollectBlocks(collectedBlocks, removedINodes); 104 // c should be contained in the children list, remove it 105 currentINode.removeChild(c); 106 } 107 createdList.clear(); 108 return counts; 109 } 110 111 /** clear the deleted list */ 112 private Quota.Counts destroyDeletedList( 113 final BlocksMapUpdateInfo collectedBlocks, 114 final List<INode> removedINodes) { 115 Quota.Counts counts = Quota.Counts.newInstance(); 116 final List<INode> deletedList = getList(ListType.DELETED); 117 for (INode d : deletedList) { 118 d.computeQuotaUsage(counts, false); 119 d.destroyAndCollectBlocks(collectedBlocks, removedINodes); 120 } 121 deletedList.clear(); 122 return counts; 123 } 124 125 /** Serialize {@link #created} */ 126 private void writeCreated(DataOutput out) throws IOException { 127 final List<INode> created = getList(ListType.CREATED); 128 out.writeInt(created.size()); 129 for (INode node : created) { 130 // For INode in created list, we only need to record its local name 131 byte[] name = node.getLocalNameBytes(); 132 out.writeShort(name.length); 133 out.write(name); 134 } 135 } 136 137 /** Serialize {@link #deleted} */ 138 private void writeDeleted(DataOutput out, 139 ReferenceMap referenceMap) throws IOException { 140 final List<INode> deleted = getList(ListType.DELETED); 141 out.writeInt(deleted.size()); 142 for (INode node : deleted) { 143 FSImageSerialization.saveINode2Image(node, out, true, referenceMap); 144 } 145 } 146 147 /** Serialize to out */ 148 private void write(DataOutput out, ReferenceMap referenceMap 149 ) throws IOException { 150 writeCreated(out); 151 writeDeleted(out, referenceMap); 152 } 153 154 /** Get the list of INodeDirectory contained in the deleted list */ 155 private void getDirsInDeleted(List<INodeDirectory> dirList) { 156 for (INode node : getList(ListType.DELETED)) { 157 if (node.isDirectory()) { 158 dirList.add(node.asDirectory()); 159 } 160 } 161 } 162 } 163 164 /** 165 * The difference of an {@link INodeDirectory} between two snapshots. 166 */ 167 public static class DirectoryDiff extends 168 AbstractINodeDiff<INodeDirectory, INodeDirectoryAttributes, DirectoryDiff> { 169 /** The size of the children list at snapshot creation time. */ 170 private final int childrenSize; 171 /** The children list diff. */ 172 private final ChildrenDiff diff; 173 private boolean isSnapshotRoot = false; 174 175 private DirectoryDiff(int snapshotId, INodeDirectory dir) { 176 super(snapshotId, null, null); 177 178 this.childrenSize = dir.getChildrenList(Snapshot.CURRENT_STATE_ID).size(); 179 this.diff = new ChildrenDiff(); 180 } 181 182 /** Constructor used by FSImage loading */ 183 DirectoryDiff(int snapshotId, INodeDirectoryAttributes snapshotINode, 184 DirectoryDiff posteriorDiff, int childrenSize, List<INode> createdList, 185 List<INode> deletedList, boolean isSnapshotRoot) { 186 super(snapshotId, snapshotINode, posteriorDiff); 187 this.childrenSize = childrenSize; 188 this.diff = new ChildrenDiff(createdList, deletedList); 189 this.isSnapshotRoot = isSnapshotRoot; 190 } 191 192 public ChildrenDiff getChildrenDiff() { 193 return diff; 194 } 195 196 void setSnapshotRoot(INodeDirectoryAttributes root) { 197 this.snapshotINode = root; 198 this.isSnapshotRoot = true; 199 } 200 201 boolean isSnapshotRoot() { 202 return isSnapshotRoot; 203 } 204 205 @Override 206 Quota.Counts combinePosteriorAndCollectBlocks( 207 final INodeDirectory currentDir, final DirectoryDiff posterior, 208 final BlocksMapUpdateInfo collectedBlocks, 209 final List<INode> removedINodes) { 210 final Quota.Counts counts = Quota.Counts.newInstance(); 211 diff.combinePosterior(posterior.diff, new Diff.Processor<INode>() { 212 /** Collect blocks for deleted files. */ 213 @Override 214 public void process(INode inode) { 215 if (inode != null) { 216 inode.computeQuotaUsage(counts, false); 217 inode.destroyAndCollectBlocks(collectedBlocks, removedINodes); 218 } 219 } 220 }); 221 return counts; 222 } 223 224 /** 225 * @return The children list of a directory in a snapshot. 226 * Since the snapshot is read-only, the logical view of the list is 227 * never changed although the internal data structure may mutate. 228 */ 229 private ReadOnlyList<INode> getChildrenList(final INodeDirectory currentDir) { 230 return new ReadOnlyList<INode>() { 231 private List<INode> children = null; 232 233 private List<INode> initChildren() { 234 if (children == null) { 235 final ChildrenDiff combined = new ChildrenDiff(); 236 for (DirectoryDiff d = DirectoryDiff.this; d != null; 237 d = d.getPosterior()) { 238 combined.combinePosterior(d.diff, null); 239 } 240 children = combined.apply2Current(ReadOnlyList.Util.asList( 241 currentDir.getChildrenList(Snapshot.CURRENT_STATE_ID))); 242 } 243 return children; 244 } 245 246 @Override 247 public Iterator<INode> iterator() { 248 return initChildren().iterator(); 249 } 250 251 @Override 252 public boolean isEmpty() { 253 return childrenSize == 0; 254 } 255 256 @Override 257 public int size() { 258 return childrenSize; 259 } 260 261 @Override 262 public INode get(int i) { 263 return initChildren().get(i); 264 } 265 }; 266 } 267 268 /** @return the child with the given name. */ 269 INode getChild(byte[] name, boolean checkPosterior, 270 INodeDirectory currentDir) { 271 for(DirectoryDiff d = this; ; d = d.getPosterior()) { 272 final Container<INode> returned = d.diff.accessPrevious(name); 273 if (returned != null) { 274 // the diff is able to determine the inode 275 return returned.getElement(); 276 } else if (!checkPosterior) { 277 // Since checkPosterior is false, return null, i.e. not found. 278 return null; 279 } else if (d.getPosterior() == null) { 280 // no more posterior diff, get from current inode. 281 return currentDir.getChild(name, Snapshot.CURRENT_STATE_ID); 282 } 283 } 284 } 285 286 @Override 287 public String toString() { 288 return super.toString() + " childrenSize=" + childrenSize + ", " + diff; 289 } 290 291 int getChildrenSize() { 292 return childrenSize; 293 } 294 295 @Override 296 void write(DataOutput out, ReferenceMap referenceMap) throws IOException { 297 writeSnapshot(out); 298 out.writeInt(childrenSize); 299 300 // Write snapshotINode 301 out.writeBoolean(isSnapshotRoot); 302 if (!isSnapshotRoot) { 303 if (snapshotINode != null) { 304 out.writeBoolean(true); 305 FSImageSerialization.writeINodeDirectoryAttributes(snapshotINode, out); 306 } else { 307 out.writeBoolean(false); 308 } 309 } 310 // Write diff. Node need to write poseriorDiff, since diffs is a list. 311 diff.write(out, referenceMap); 312 } 313 314 @Override 315 Quota.Counts destroyDiffAndCollectBlocks(INodeDirectory currentINode, 316 BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) { 317 // this diff has been deleted 318 Quota.Counts counts = Quota.Counts.newInstance(); 319 counts.add(diff.destroyDeletedList(collectedBlocks, removedINodes)); 320 return counts; 321 } 322 } 323 324 /** A list of directory diffs. */ 325 public static class DirectoryDiffList 326 extends AbstractINodeDiffList<INodeDirectory, INodeDirectoryAttributes, DirectoryDiff> { 327 328 @Override 329 DirectoryDiff createDiff(int snapshot, INodeDirectory currentDir) { 330 return new DirectoryDiff(snapshot, currentDir); 331 } 332 333 @Override 334 INodeDirectoryAttributes createSnapshotCopy(INodeDirectory currentDir) { 335 return currentDir.isQuotaSet()? 336 new INodeDirectoryAttributes.CopyWithQuota(currentDir) 337 : new INodeDirectoryAttributes.SnapshotCopy(currentDir); 338 } 339 340 /** Replace the given child in the created/deleted list, if there is any. */ 341 public boolean replaceChild(final ListType type, final INode oldChild, 342 final INode newChild) { 343 final List<DirectoryDiff> diffList = asList(); 344 for(int i = diffList.size() - 1; i >= 0; i--) { 345 final ChildrenDiff diff = diffList.get(i).diff; 346 if (diff.replace(type, oldChild, newChild)) { 347 return true; 348 } 349 } 350 return false; 351 } 352 353 /** Remove the given child in the created/deleted list, if there is any. */ 354 public boolean removeChild(final ListType type, final INode child) { 355 final List<DirectoryDiff> diffList = asList(); 356 for(int i = diffList.size() - 1; i >= 0; i--) { 357 final ChildrenDiff diff = diffList.get(i).diff; 358 if (diff.removeChild(type, child)) { 359 return true; 360 } 361 } 362 return false; 363 } 364 365 /** 366 * Find the corresponding snapshot whose deleted list contains the given 367 * inode. 368 * @return the id of the snapshot. {@link Snapshot#NO_SNAPSHOT_ID} if the 369 * given inode is not in any of the snapshot. 370 */ 371 public int findSnapshotDeleted(final INode child) { 372 final List<DirectoryDiff> diffList = asList(); 373 for(int i = diffList.size() - 1; i >= 0; i--) { 374 final ChildrenDiff diff = diffList.get(i).diff; 375 final int d = diff.searchIndex(ListType.DELETED, 376 child.getLocalNameBytes()); 377 if (d >= 0 && diff.getList(ListType.DELETED).get(d) == child) { 378 return diffList.get(i).getSnapshotId(); 379 } 380 } 381 return Snapshot.NO_SNAPSHOT_ID; 382 } 383 } 384 385 private static Map<INode, INode> cloneDiffList(List<INode> diffList) { 386 if (diffList == null || diffList.size() == 0) { 387 return null; 388 } 389 Map<INode, INode> map = new HashMap<INode, INode>(diffList.size()); 390 for (INode node : diffList) { 391 map.put(node, node); 392 } 393 return map; 394 } 395 396 /** 397 * Destroy a subtree under a DstReference node. 398 */ 399 public static void destroyDstSubtree(INode inode, final int snapshot, 400 final int prior, final BlocksMapUpdateInfo collectedBlocks, 401 final List<INode> removedINodes) throws QuotaExceededException { 402 Preconditions.checkArgument(prior != Snapshot.NO_SNAPSHOT_ID); 403 if (inode.isReference()) { 404 if (inode instanceof INodeReference.WithName 405 && snapshot != Snapshot.CURRENT_STATE_ID) { 406 // this inode has been renamed before the deletion of the DstReference 407 // subtree 408 inode.cleanSubtree(snapshot, prior, collectedBlocks, removedINodes, 409 true); 410 } else { 411 // for DstReference node, continue this process to its subtree 412 destroyDstSubtree(inode.asReference().getReferredINode(), snapshot, 413 prior, collectedBlocks, removedINodes); 414 } 415 } else if (inode.isFile()) { 416 inode.cleanSubtree(snapshot, prior, collectedBlocks, removedINodes, true); 417 } else if (inode.isDirectory()) { 418 Map<INode, INode> excludedNodes = null; 419 INodeDirectory dir = inode.asDirectory(); 420 DirectoryWithSnapshotFeature sf = dir.getDirectoryWithSnapshotFeature(); 421 if (sf != null) { 422 DirectoryDiffList diffList = sf.getDiffs(); 423 DirectoryDiff priorDiff = diffList.getDiffById(prior); 424 if (priorDiff != null && priorDiff.getSnapshotId() == prior) { 425 List<INode> dList = priorDiff.diff.getList(ListType.DELETED); 426 excludedNodes = cloneDiffList(dList); 427 } 428 429 if (snapshot != Snapshot.CURRENT_STATE_ID) { 430 diffList.deleteSnapshotDiff(snapshot, prior, dir, collectedBlocks, 431 removedINodes, true); 432 } 433 priorDiff = diffList.getDiffById(prior); 434 if (priorDiff != null && priorDiff.getSnapshotId() == prior) { 435 priorDiff.diff.destroyCreatedList(dir, collectedBlocks, 436 removedINodes); 437 } 438 } 439 for (INode child : inode.asDirectory().getChildrenList(prior)) { 440 if (excludedNodes != null && excludedNodes.containsKey(child)) { 441 continue; 442 } 443 destroyDstSubtree(child, snapshot, prior, collectedBlocks, 444 removedINodes); 445 } 446 } 447 } 448 449 /** 450 * Clean an inode while we move it from the deleted list of post to the 451 * deleted list of prior. 452 * @param inode The inode to clean. 453 * @param post The post snapshot. 454 * @param prior The id of the prior snapshot. 455 * @param collectedBlocks Used to collect blocks for later deletion. 456 * @return Quota usage update. 457 */ 458 private static Quota.Counts cleanDeletedINode(INode inode, 459 final int post, final int prior, 460 final BlocksMapUpdateInfo collectedBlocks, 461 final List<INode> removedINodes, final boolean countDiffChange) 462 throws QuotaExceededException { 463 Quota.Counts counts = Quota.Counts.newInstance(); 464 Deque<INode> queue = new ArrayDeque<INode>(); 465 queue.addLast(inode); 466 while (!queue.isEmpty()) { 467 INode topNode = queue.pollFirst(); 468 if (topNode instanceof INodeReference.WithName) { 469 INodeReference.WithName wn = (INodeReference.WithName) topNode; 470 if (wn.getLastSnapshotId() >= post) { 471 wn.cleanSubtree(post, prior, collectedBlocks, removedINodes, 472 countDiffChange); 473 } 474 // For DstReference node, since the node is not in the created list of 475 // prior, we should treat it as regular file/dir 476 } else if (topNode.isFile() && topNode.asFile().isWithSnapshot()) { 477 INodeFile file = topNode.asFile(); 478 counts.add(file.getDiffs().deleteSnapshotDiff(post, prior, file, 479 collectedBlocks, removedINodes, countDiffChange)); 480 } else if (topNode.isDirectory()) { 481 INodeDirectory dir = topNode.asDirectory(); 482 ChildrenDiff priorChildrenDiff = null; 483 DirectoryWithSnapshotFeature sf = dir.getDirectoryWithSnapshotFeature(); 484 if (sf != null) { 485 // delete files/dirs created after prior. Note that these 486 // files/dirs, along with inode, were deleted right after post. 487 DirectoryDiff priorDiff = sf.getDiffs().getDiffById(prior); 488 if (priorDiff != null && priorDiff.getSnapshotId() == prior) { 489 priorChildrenDiff = priorDiff.getChildrenDiff(); 490 counts.add(priorChildrenDiff.destroyCreatedList(dir, 491 collectedBlocks, removedINodes)); 492 } 493 } 494 495 for (INode child : dir.getChildrenList(prior)) { 496 if (priorChildrenDiff != null 497 && priorChildrenDiff.search(ListType.DELETED, 498 child.getLocalNameBytes()) != null) { 499 continue; 500 } 501 queue.addLast(child); 502 } 503 } 504 } 505 return counts; 506 } 507 508 /** Diff list sorted by snapshot IDs, i.e. in chronological order. */ 509 private final DirectoryDiffList diffs; 510 511 public DirectoryWithSnapshotFeature(DirectoryDiffList diffs) { 512 this.diffs = diffs != null ? diffs : new DirectoryDiffList(); 513 } 514 515 /** @return the last snapshot. */ 516 public int getLastSnapshotId() { 517 return diffs.getLastSnapshotId(); 518 } 519 520 /** @return the snapshot diff list. */ 521 public DirectoryDiffList getDiffs() { 522 return diffs; 523 } 524 525 /** 526 * Get all the directories that are stored in some snapshot but not in the 527 * current children list. These directories are equivalent to the directories 528 * stored in the deletes lists. 529 */ 530 public void getSnapshotDirectory(List<INodeDirectory> snapshotDir) { 531 for (DirectoryDiff sdiff : diffs) { 532 sdiff.getChildrenDiff().getDirsInDeleted(snapshotDir); 533 } 534 } 535 536 /** 537 * Add an inode into parent's children list. The caller of this method needs 538 * to make sure that parent is in the given snapshot "latest". 539 */ 540 public boolean addChild(INodeDirectory parent, INode inode, 541 boolean setModTime, int latestSnapshotId) throws QuotaExceededException { 542 ChildrenDiff diff = diffs.checkAndAddLatestSnapshotDiff(latestSnapshotId, 543 parent).diff; 544 int undoInfo = diff.create(inode); 545 546 final boolean added = parent.addChild(inode, setModTime, 547 Snapshot.CURRENT_STATE_ID); 548 if (!added) { 549 diff.undoCreate(inode, undoInfo); 550 } 551 return added; 552 } 553 554 /** 555 * Remove an inode from parent's children list. The caller of this method 556 * needs to make sure that parent is in the given snapshot "latest". 557 */ 558 public boolean removeChild(INodeDirectory parent, INode child, 559 int latestSnapshotId) throws QuotaExceededException { 560 // For a directory that is not a renamed node, if isInLatestSnapshot returns 561 // false, the directory is not in the latest snapshot, thus we do not need 562 // to record the removed child in any snapshot. 563 // For a directory that was moved/renamed, note that if the directory is in 564 // any of the previous snapshots, we will create a reference node for the 565 // directory while rename, and isInLatestSnapshot will return true in that 566 // scenario (if all previous snapshots have been deleted, isInLatestSnapshot 567 // still returns false). Thus if isInLatestSnapshot returns false, the 568 // directory node cannot be in any snapshot (not in current tree, nor in 569 // previous src tree). Thus we do not need to record the removed child in 570 // any snapshot. 571 ChildrenDiff diff = diffs.checkAndAddLatestSnapshotDiff(latestSnapshotId, 572 parent).diff; 573 UndoInfo<INode> undoInfo = diff.delete(child); 574 575 final boolean removed = parent.removeChild(child); 576 if (!removed && undoInfo != null) { 577 // remove failed, undo 578 diff.undoDelete(child, undoInfo); 579 } 580 return removed; 581 } 582 583 /** 584 * @return If there is no corresponding directory diff for the given 585 * snapshot, this means that the current children list should be 586 * returned for the snapshot. Otherwise we calculate the children list 587 * for the snapshot and return it. 588 */ 589 public ReadOnlyList<INode> getChildrenList(INodeDirectory currentINode, 590 final int snapshotId) { 591 final DirectoryDiff diff = diffs.getDiffById(snapshotId); 592 return diff != null ? diff.getChildrenList(currentINode) : currentINode 593 .getChildrenList(Snapshot.CURRENT_STATE_ID); 594 } 595 596 public INode getChild(INodeDirectory currentINode, byte[] name, 597 int snapshotId) { 598 final DirectoryDiff diff = diffs.getDiffById(snapshotId); 599 return diff != null ? diff.getChild(name, true, currentINode) 600 : currentINode.getChild(name, Snapshot.CURRENT_STATE_ID); 601 } 602 603 /** Used to record the modification of a symlink node */ 604 public INode saveChild2Snapshot(INodeDirectory currentINode, 605 final INode child, final int latestSnapshotId, final INode snapshotCopy) 606 throws QuotaExceededException { 607 Preconditions.checkArgument(!child.isDirectory(), 608 "child is a directory, child=%s", child); 609 Preconditions.checkArgument(latestSnapshotId != Snapshot.CURRENT_STATE_ID); 610 611 final DirectoryDiff diff = diffs.checkAndAddLatestSnapshotDiff( 612 latestSnapshotId, currentINode); 613 if (diff.getChild(child.getLocalNameBytes(), false, currentINode) != null) { 614 // it was already saved in the latest snapshot earlier. 615 return child; 616 } 617 618 diff.diff.modify(snapshotCopy, child); 619 return child; 620 } 621 622 public void clear(INodeDirectory currentINode, 623 final BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) { 624 // destroy its diff list 625 for (DirectoryDiff diff : diffs) { 626 diff.destroyDiffAndCollectBlocks(currentINode, collectedBlocks, 627 removedINodes); 628 } 629 diffs.clear(); 630 } 631 632 public Quota.Counts computeQuotaUsage4CurrentDirectory(Quota.Counts counts) { 633 for(DirectoryDiff d : diffs) { 634 for(INode deleted : d.getChildrenDiff().getList(ListType.DELETED)) { 635 deleted.computeQuotaUsage(counts, false, Snapshot.CURRENT_STATE_ID); 636 } 637 } 638 counts.add(Quota.NAMESPACE, diffs.asList().size()); 639 return counts; 640 } 641 642 public void computeContentSummary4Snapshot(final Content.Counts counts) { 643 // Create a new blank summary context for blocking processing of subtree. 644 ContentSummaryComputationContext summary = 645 new ContentSummaryComputationContext(); 646 for(DirectoryDiff d : diffs) { 647 for(INode deleted : d.getChildrenDiff().getList(ListType.DELETED)) { 648 deleted.computeContentSummary(summary); 649 } 650 } 651 // Add the counts from deleted trees. 652 counts.add(summary.getCounts()); 653 // Add the deleted directory count. 654 counts.add(Content.DIRECTORY, diffs.asList().size()); 655 } 656 657 /** 658 * Compute the difference between Snapshots. 659 * 660 * @param fromSnapshot Start point of the diff computation. Null indicates 661 * current tree. 662 * @param toSnapshot End point of the diff computation. Null indicates current 663 * tree. 664 * @param diff Used to capture the changes happening to the children. Note 665 * that the diff still represents (later_snapshot - earlier_snapshot) 666 * although toSnapshot can be before fromSnapshot. 667 * @param currentINode The {@link INodeDirectory} this feature belongs to. 668 * @return Whether changes happened between the startSnapshot and endSnaphsot. 669 */ 670 boolean computeDiffBetweenSnapshots(Snapshot fromSnapshot, 671 Snapshot toSnapshot, ChildrenDiff diff, INodeDirectory currentINode) { 672 int[] diffIndexPair = diffs.changedBetweenSnapshots(fromSnapshot, 673 toSnapshot); 674 if (diffIndexPair == null) { 675 return false; 676 } 677 int earlierDiffIndex = diffIndexPair[0]; 678 int laterDiffIndex = diffIndexPair[1]; 679 680 boolean dirMetadataChanged = false; 681 INodeDirectoryAttributes dirCopy = null; 682 List<DirectoryDiff> difflist = diffs.asList(); 683 for (int i = earlierDiffIndex; i < laterDiffIndex; i++) { 684 DirectoryDiff sdiff = difflist.get(i); 685 diff.combinePosterior(sdiff.diff, null); 686 if (!dirMetadataChanged && sdiff.snapshotINode != null) { 687 if (dirCopy == null) { 688 dirCopy = sdiff.snapshotINode; 689 } else if (!dirCopy.metadataEquals(sdiff.snapshotINode)) { 690 dirMetadataChanged = true; 691 } 692 } 693 } 694 695 if (!diff.isEmpty() || dirMetadataChanged) { 696 return true; 697 } else if (dirCopy != null) { 698 for (int i = laterDiffIndex; i < difflist.size(); i++) { 699 if (!dirCopy.metadataEquals(difflist.get(i).snapshotINode)) { 700 return true; 701 } 702 } 703 return !dirCopy.metadataEquals(currentINode); 704 } else { 705 return false; 706 } 707 } 708 709 public Quota.Counts cleanDirectory(final INodeDirectory currentINode, 710 final int snapshot, int prior, 711 final BlocksMapUpdateInfo collectedBlocks, 712 final List<INode> removedINodes, final boolean countDiffChange) 713 throws QuotaExceededException { 714 Quota.Counts counts = Quota.Counts.newInstance(); 715 Map<INode, INode> priorCreated = null; 716 Map<INode, INode> priorDeleted = null; 717 if (snapshot == Snapshot.CURRENT_STATE_ID) { // delete the current directory 718 currentINode.recordModification(prior); 719 // delete everything in created list 720 DirectoryDiff lastDiff = diffs.getLast(); 721 if (lastDiff != null) { 722 counts.add(lastDiff.diff.destroyCreatedList(currentINode, 723 collectedBlocks, removedINodes)); 724 } 725 counts.add(currentINode.cleanSubtreeRecursively(snapshot, prior, 726 collectedBlocks, removedINodes, priorDeleted, countDiffChange)); 727 } else { 728 // update prior 729 prior = getDiffs().updatePrior(snapshot, prior); 730 // if there is a snapshot diff associated with prior, we need to record 731 // its original created and deleted list before deleting post 732 if (prior != Snapshot.NO_SNAPSHOT_ID) { 733 DirectoryDiff priorDiff = this.getDiffs().getDiffById(prior); 734 if (priorDiff != null && priorDiff.getSnapshotId() == prior) { 735 List<INode> cList = priorDiff.diff.getList(ListType.CREATED); 736 List<INode> dList = priorDiff.diff.getList(ListType.DELETED); 737 priorCreated = cloneDiffList(cList); 738 priorDeleted = cloneDiffList(dList); 739 } 740 } 741 742 counts.add(getDiffs().deleteSnapshotDiff(snapshot, prior, 743 currentINode, collectedBlocks, removedINodes, countDiffChange)); 744 counts.add(currentINode.cleanSubtreeRecursively(snapshot, prior, 745 collectedBlocks, removedINodes, priorDeleted, countDiffChange)); 746 747 // check priorDiff again since it may be created during the diff deletion 748 if (prior != Snapshot.NO_SNAPSHOT_ID) { 749 DirectoryDiff priorDiff = this.getDiffs().getDiffById(prior); 750 if (priorDiff != null && priorDiff.getSnapshotId() == prior) { 751 // For files/directories created between "prior" and "snapshot", 752 // we need to clear snapshot copies for "snapshot". Note that we must 753 // use null as prior in the cleanSubtree call. Files/directories that 754 // were created before "prior" will be covered by the later 755 // cleanSubtreeRecursively call. 756 if (priorCreated != null) { 757 // we only check the node originally in prior's created list 758 for (INode cNode : priorDiff.getChildrenDiff().getList( 759 ListType.CREATED)) { 760 if (priorCreated.containsKey(cNode)) { 761 counts.add(cNode.cleanSubtree(snapshot, Snapshot.NO_SNAPSHOT_ID, 762 collectedBlocks, removedINodes, countDiffChange)); 763 } 764 } 765 } 766 767 // When a directory is moved from the deleted list of the posterior 768 // diff to the deleted list of this diff, we need to destroy its 769 // descendants that were 1) created after taking this diff and 2) 770 // deleted after taking posterior diff. 771 772 // For files moved from posterior's deleted list, we also need to 773 // delete its snapshot copy associated with the posterior snapshot. 774 775 for (INode dNode : priorDiff.getChildrenDiff().getList( 776 ListType.DELETED)) { 777 if (priorDeleted == null || !priorDeleted.containsKey(dNode)) { 778 counts.add(cleanDeletedINode(dNode, snapshot, prior, 779 collectedBlocks, removedINodes, countDiffChange)); 780 } 781 } 782 } 783 } 784 } 785 786 if (currentINode.isQuotaSet()) { 787 currentINode.getDirectoryWithQuotaFeature().addSpaceConsumed2Cache( 788 -counts.get(Quota.NAMESPACE), -counts.get(Quota.DISKSPACE)); 789 } 790 return counts; 791 } 792}