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.snapshot;
019
020 import java.io.DataOutput;
021 import java.io.IOException;
022 import java.util.ArrayDeque;
023 import java.util.Deque;
024 import java.util.HashMap;
025 import java.util.Iterator;
026 import java.util.List;
027 import java.util.Map;
028
029 import org.apache.hadoop.classification.InterfaceAudience;
030 import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
031 import org.apache.hadoop.hdfs.server.namenode.Content;
032 import org.apache.hadoop.hdfs.server.namenode.ContentSummaryComputationContext;
033 import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization;
034 import org.apache.hadoop.hdfs.server.namenode.INode;
035 import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo;
036 import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
037 import org.apache.hadoop.hdfs.server.namenode.INodeDirectoryAttributes;
038 import org.apache.hadoop.hdfs.server.namenode.INodeFile;
039 import org.apache.hadoop.hdfs.server.namenode.INodeReference;
040 import org.apache.hadoop.hdfs.server.namenode.Quota;
041 import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotFSImageFormat.ReferenceMap;
042 import org.apache.hadoop.hdfs.util.Diff;
043 import org.apache.hadoop.hdfs.util.Diff.Container;
044 import org.apache.hadoop.hdfs.util.Diff.ListType;
045 import org.apache.hadoop.hdfs.util.Diff.UndoInfo;
046 import org.apache.hadoop.hdfs.util.ReadOnlyList;
047
048 import 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
056 public 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 }