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