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