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