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.ArrayList;
024 import java.util.Collections;
025 import java.util.Deque;
026 import java.util.HashMap;
027 import java.util.Iterator;
028 import java.util.List;
029 import java.util.Map;
030
031 import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
032 import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffReportEntry;
033 import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffType;
034 import org.apache.hadoop.hdfs.server.namenode.Content;
035 import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization;
036 import org.apache.hadoop.hdfs.server.namenode.INode;
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.INodeDirectoryWithQuota;
040 import org.apache.hadoop.hdfs.server.namenode.INodeMap;
041 import org.apache.hadoop.hdfs.server.namenode.INodeReference;
042 import org.apache.hadoop.hdfs.server.namenode.Quota;
043 import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotFSImageFormat.ReferenceMap;
044 import org.apache.hadoop.hdfs.util.Diff;
045 import org.apache.hadoop.hdfs.util.Diff.Container;
046 import org.apache.hadoop.hdfs.util.Diff.ListType;
047 import org.apache.hadoop.hdfs.util.Diff.UndoInfo;
048 import org.apache.hadoop.hdfs.util.ReadOnlyList;
049
050 import com.google.common.base.Preconditions;
051
052 /**
053 * The directory with snapshots. It maintains a list of snapshot diffs for
054 * storing snapshot data. When there are modifications to the directory, the old
055 * data is stored in the latest snapshot, if there is any.
056 */
057 public class INodeDirectoryWithSnapshot extends INodeDirectoryWithQuota {
058 /**
059 * The difference between the current state and a previous snapshot
060 * of the children list of an INodeDirectory.
061 */
062 static class ChildrenDiff extends Diff<byte[], INode> {
063 ChildrenDiff() {}
064
065 private ChildrenDiff(final List<INode> created, final List<INode> deleted) {
066 super(created, deleted);
067 }
068
069 /**
070 * Replace the given child from the created/deleted list.
071 * @return true if the child is replaced; false if the child is not found.
072 */
073 private final boolean replace(final ListType type,
074 final INode oldChild, final INode newChild) {
075 final List<INode> list = getList(type);
076 final int i = search(list, oldChild.getLocalNameBytes());
077 if (i < 0) {
078 return false;
079 }
080
081 final INode removed = list.set(i, newChild);
082 Preconditions.checkState(removed == oldChild);
083 return true;
084 }
085
086 private final boolean removeChild(ListType type, final INode child) {
087 final List<INode> list = getList(type);
088 final int i = searchIndex(type, child.getLocalNameBytes());
089 if (i >= 0 && list.get(i) == child) {
090 list.remove(i);
091 return true;
092 }
093 return false;
094 }
095
096 /** clear the created list */
097 private Quota.Counts destroyCreatedList(
098 final INodeDirectoryWithSnapshot currentINode,
099 final BlocksMapUpdateInfo collectedBlocks,
100 final List<INode> removedINodes) {
101 Quota.Counts counts = Quota.Counts.newInstance();
102 final List<INode> createdList = getList(ListType.CREATED);
103 for (INode c : createdList) {
104 c.computeQuotaUsage(counts, true);
105 c.destroyAndCollectBlocks(collectedBlocks, removedINodes);
106 // c should be contained in the children list, remove it
107 currentINode.removeChild(c);
108 }
109 createdList.clear();
110 return counts;
111 }
112
113 /** clear the deleted list */
114 private Quota.Counts destroyDeletedList(
115 final BlocksMapUpdateInfo collectedBlocks,
116 final List<INode> removedINodes) {
117 Quota.Counts counts = Quota.Counts.newInstance();
118 final List<INode> deletedList = getList(ListType.DELETED);
119 for (INode d : deletedList) {
120 d.computeQuotaUsage(counts, false);
121 d.destroyAndCollectBlocks(collectedBlocks, removedINodes);
122 }
123 deletedList.clear();
124 return counts;
125 }
126
127 /** Serialize {@link #created} */
128 private void writeCreated(DataOutput out) throws IOException {
129 final List<INode> created = getList(ListType.CREATED);
130 out.writeInt(created.size());
131 for (INode node : created) {
132 // For INode in created list, we only need to record its local name
133 byte[] name = node.getLocalNameBytes();
134 out.writeShort(name.length);
135 out.write(name);
136 }
137 }
138
139 /** Serialize {@link #deleted} */
140 private void writeDeleted(DataOutput out,
141 ReferenceMap referenceMap) throws IOException {
142 final List<INode> deleted = getList(ListType.DELETED);
143 out.writeInt(deleted.size());
144 for (INode node : deleted) {
145 FSImageSerialization.saveINode2Image(node, out, true, referenceMap);
146 }
147 }
148
149 /** Serialize to out */
150 private void write(DataOutput out, ReferenceMap referenceMap
151 ) throws IOException {
152 writeCreated(out);
153 writeDeleted(out, referenceMap);
154 }
155
156 /** Get the list of INodeDirectory contained in the deleted list */
157 private void getDirsInDeleted(List<INodeDirectory> dirList) {
158 for (INode node : getList(ListType.DELETED)) {
159 if (node.isDirectory()) {
160 dirList.add(node.asDirectory());
161 }
162 }
163 }
164
165 /**
166 * Interpret the diff and generate a list of {@link DiffReportEntry}.
167 * @param parentPath The relative path of the parent.
168 * @param parent The directory that the diff belongs to.
169 * @param fromEarlier True indicates {@code diff=later-earlier},
170 * False indicates {@code diff=earlier-later}
171 * @return A list of {@link DiffReportEntry} as the diff report.
172 */
173 public List<DiffReportEntry> generateReport(byte[][] parentPath,
174 INodeDirectoryWithSnapshot parent, boolean fromEarlier) {
175 List<DiffReportEntry> cList = new ArrayList<DiffReportEntry>();
176 List<DiffReportEntry> dList = new ArrayList<DiffReportEntry>();
177 int c = 0, d = 0;
178 List<INode> created = getList(ListType.CREATED);
179 List<INode> deleted = getList(ListType.DELETED);
180 byte[][] fullPath = new byte[parentPath.length + 1][];
181 System.arraycopy(parentPath, 0, fullPath, 0, parentPath.length);
182 for (; c < created.size() && d < deleted.size(); ) {
183 INode cnode = created.get(c);
184 INode dnode = deleted.get(d);
185 if (cnode.compareTo(dnode.getLocalNameBytes()) == 0) {
186 fullPath[fullPath.length - 1] = cnode.getLocalNameBytes();
187 if (cnode.isSymlink() && dnode.isSymlink()) {
188 dList.add(new DiffReportEntry(DiffType.MODIFY, fullPath));
189 } else {
190 // must be the case: delete first and then create an inode with the
191 // same name
192 cList.add(new DiffReportEntry(DiffType.CREATE, fullPath));
193 dList.add(new DiffReportEntry(DiffType.DELETE, fullPath));
194 }
195 c++;
196 d++;
197 } else if (cnode.compareTo(dnode.getLocalNameBytes()) < 0) {
198 fullPath[fullPath.length - 1] = cnode.getLocalNameBytes();
199 cList.add(new DiffReportEntry(fromEarlier ? DiffType.CREATE
200 : DiffType.DELETE, fullPath));
201 c++;
202 } else {
203 fullPath[fullPath.length - 1] = dnode.getLocalNameBytes();
204 dList.add(new DiffReportEntry(fromEarlier ? DiffType.DELETE
205 : DiffType.CREATE, fullPath));
206 d++;
207 }
208 }
209 for (; d < deleted.size(); d++) {
210 fullPath[fullPath.length - 1] = deleted.get(d).getLocalNameBytes();
211 dList.add(new DiffReportEntry(fromEarlier ? DiffType.DELETE
212 : DiffType.CREATE, fullPath));
213 }
214 for (; c < created.size(); c++) {
215 fullPath[fullPath.length - 1] = created.get(c).getLocalNameBytes();
216 cList.add(new DiffReportEntry(fromEarlier ? DiffType.CREATE
217 : DiffType.DELETE, fullPath));
218 }
219 dList.addAll(cList);
220 return dList;
221 }
222 }
223
224 /**
225 * The difference of an {@link INodeDirectory} between two snapshots.
226 */
227 public static class DirectoryDiff extends
228 AbstractINodeDiff<INodeDirectory, INodeDirectoryAttributes, DirectoryDiff> {
229 /** The size of the children list at snapshot creation time. */
230 private final int childrenSize;
231 /** The children list diff. */
232 private final ChildrenDiff diff;
233
234 private DirectoryDiff(Snapshot snapshot, INodeDirectory dir) {
235 super(snapshot, null, null);
236
237 this.childrenSize = dir.getChildrenList(null).size();
238 this.diff = new ChildrenDiff();
239 }
240
241 /** Constructor used by FSImage loading */
242 DirectoryDiff(Snapshot snapshot, INodeDirectoryAttributes snapshotINode,
243 DirectoryDiff posteriorDiff, int childrenSize,
244 List<INode> createdList, List<INode> deletedList) {
245 super(snapshot, snapshotINode, posteriorDiff);
246 this.childrenSize = childrenSize;
247 this.diff = new ChildrenDiff(createdList, deletedList);
248 }
249
250 ChildrenDiff getChildrenDiff() {
251 return diff;
252 }
253
254 /** Is the inode the root of the snapshot? */
255 boolean isSnapshotRoot() {
256 return snapshotINode == snapshot.getRoot();
257 }
258
259 @Override
260 Quota.Counts combinePosteriorAndCollectBlocks(
261 final INodeDirectory currentDir, final DirectoryDiff posterior,
262 final BlocksMapUpdateInfo collectedBlocks,
263 final List<INode> removedINodes) {
264 final Quota.Counts counts = Quota.Counts.newInstance();
265 diff.combinePosterior(posterior.diff, new Diff.Processor<INode>() {
266 /** Collect blocks for deleted files. */
267 @Override
268 public void process(INode inode) {
269 if (inode != null) {
270 inode.computeQuotaUsage(counts, false);
271 inode.destroyAndCollectBlocks(collectedBlocks, removedINodes);
272 }
273 }
274 });
275 return counts;
276 }
277
278 /**
279 * @return The children list of a directory in a snapshot.
280 * Since the snapshot is read-only, the logical view of the list is
281 * never changed although the internal data structure may mutate.
282 */
283 ReadOnlyList<INode> getChildrenList(final INodeDirectory currentDir) {
284 return new ReadOnlyList<INode>() {
285 private List<INode> children = null;
286
287 private List<INode> initChildren() {
288 if (children == null) {
289 final ChildrenDiff combined = new ChildrenDiff();
290 for(DirectoryDiff d = DirectoryDiff.this; d != null; d = d.getPosterior()) {
291 combined.combinePosterior(d.diff, null);
292 }
293 children = combined.apply2Current(ReadOnlyList.Util.asList(
294 currentDir.getChildrenList(null)));
295 }
296 return children;
297 }
298
299 @Override
300 public Iterator<INode> iterator() {
301 return initChildren().iterator();
302 }
303
304 @Override
305 public boolean isEmpty() {
306 return childrenSize == 0;
307 }
308
309 @Override
310 public int size() {
311 return childrenSize;
312 }
313
314 @Override
315 public INode get(int i) {
316 return initChildren().get(i);
317 }
318 };
319 }
320
321 /** @return the child with the given name. */
322 INode getChild(byte[] name, boolean checkPosterior,
323 INodeDirectory currentDir) {
324 for(DirectoryDiff d = this; ; d = d.getPosterior()) {
325 final Container<INode> returned = d.diff.accessPrevious(name);
326 if (returned != null) {
327 // the diff is able to determine the inode
328 return returned.getElement();
329 } else if (!checkPosterior) {
330 // Since checkPosterior is false, return null, i.e. not found.
331 return null;
332 } else if (d.getPosterior() == null) {
333 // no more posterior diff, get from current inode.
334 return currentDir.getChild(name, null);
335 }
336 }
337 }
338
339 @Override
340 public String toString() {
341 return super.toString() + " childrenSize=" + childrenSize + ", " + diff;
342 }
343
344 @Override
345 void write(DataOutput out, ReferenceMap referenceMap) throws IOException {
346 writeSnapshot(out);
347 out.writeInt(childrenSize);
348
349 // write snapshotINode
350 if (isSnapshotRoot()) {
351 out.writeBoolean(true);
352 } else {
353 out.writeBoolean(false);
354 if (snapshotINode != null) {
355 out.writeBoolean(true);
356 FSImageSerialization.writeINodeDirectoryAttributes(snapshotINode, out);
357 } else {
358 out.writeBoolean(false);
359 }
360 }
361 // Write diff. Node need to write poseriorDiff, since diffs is a list.
362 diff.write(out, referenceMap);
363 }
364
365 @Override
366 Quota.Counts destroyDiffAndCollectBlocks(INodeDirectory currentINode,
367 BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) {
368 // this diff has been deleted
369 Quota.Counts counts = Quota.Counts.newInstance();
370 counts.add(diff.destroyDeletedList(collectedBlocks, removedINodes));
371 return counts;
372 }
373 }
374
375 /** A list of directory diffs. */
376 public static class DirectoryDiffList
377 extends AbstractINodeDiffList<INodeDirectory, INodeDirectoryAttributes, DirectoryDiff> {
378
379 @Override
380 DirectoryDiff createDiff(Snapshot snapshot, INodeDirectory currentDir) {
381 return new DirectoryDiff(snapshot, currentDir);
382 }
383
384 @Override
385 INodeDirectoryAttributes createSnapshotCopy(INodeDirectory currentDir) {
386 return currentDir.isQuotaSet()?
387 new INodeDirectoryAttributes.CopyWithQuota(currentDir)
388 : new INodeDirectoryAttributes.SnapshotCopy(currentDir);
389 }
390
391 /** Replace the given child in the created/deleted list, if there is any. */
392 private boolean replaceChild(final ListType type, final INode oldChild,
393 final INode newChild) {
394 final List<DirectoryDiff> diffList = asList();
395 for(int i = diffList.size() - 1; i >= 0; i--) {
396 final ChildrenDiff diff = diffList.get(i).diff;
397 if (diff.replace(type, oldChild, newChild)) {
398 return true;
399 }
400 }
401 return false;
402 }
403
404 /** Remove the given child in the created/deleted list, if there is any. */
405 private boolean removeChild(final ListType type, final INode child) {
406 final List<DirectoryDiff> diffList = asList();
407 for(int i = diffList.size() - 1; i >= 0; i--) {
408 final ChildrenDiff diff = diffList.get(i).diff;
409 if (diff.removeChild(type, child)) {
410 return true;
411 }
412 }
413 return false;
414 }
415 }
416
417 /**
418 * Compute the difference between Snapshots.
419 *
420 * @param fromSnapshot Start point of the diff computation. Null indicates
421 * current tree.
422 * @param toSnapshot End point of the diff computation. Null indicates current
423 * tree.
424 * @param diff Used to capture the changes happening to the children. Note
425 * that the diff still represents (later_snapshot - earlier_snapshot)
426 * although toSnapshot can be before fromSnapshot.
427 * @return Whether changes happened between the startSnapshot and endSnaphsot.
428 */
429 boolean computeDiffBetweenSnapshots(Snapshot fromSnapshot,
430 Snapshot toSnapshot, ChildrenDiff diff) {
431 Snapshot earlier = fromSnapshot;
432 Snapshot later = toSnapshot;
433 if (Snapshot.ID_COMPARATOR.compare(fromSnapshot, toSnapshot) > 0) {
434 earlier = toSnapshot;
435 later = fromSnapshot;
436 }
437
438 boolean modified = diffs.changedBetweenSnapshots(earlier,
439 later);
440 if (!modified) {
441 return false;
442 }
443
444 final List<DirectoryDiff> difflist = diffs.asList();
445 final int size = difflist.size();
446 int earlierDiffIndex = Collections.binarySearch(difflist, earlier.getId());
447 int laterDiffIndex = later == null ? size : Collections
448 .binarySearch(difflist, later.getId());
449 earlierDiffIndex = earlierDiffIndex < 0 ? (-earlierDiffIndex - 1)
450 : earlierDiffIndex;
451 laterDiffIndex = laterDiffIndex < 0 ? (-laterDiffIndex - 1)
452 : laterDiffIndex;
453
454 boolean dirMetadataChanged = false;
455 INodeDirectoryAttributes dirCopy = null;
456 for (int i = earlierDiffIndex; i < laterDiffIndex; i++) {
457 DirectoryDiff sdiff = difflist.get(i);
458 diff.combinePosterior(sdiff.diff, null);
459 if (dirMetadataChanged == false && sdiff.snapshotINode != null) {
460 if (dirCopy == null) {
461 dirCopy = sdiff.snapshotINode;
462 } else if (!dirCopy.metadataEquals(sdiff.snapshotINode)) {
463 dirMetadataChanged = true;
464 }
465 }
466 }
467
468 if (!diff.isEmpty() || dirMetadataChanged) {
469 return true;
470 } else if (dirCopy != null) {
471 for (int i = laterDiffIndex; i < size; i++) {
472 if (!dirCopy.metadataEquals(difflist.get(i).snapshotINode)) {
473 return true;
474 }
475 }
476 return !dirCopy.metadataEquals(this);
477 } else {
478 return false;
479 }
480 }
481
482 /** Diff list sorted by snapshot IDs, i.e. in chronological order. */
483 private final DirectoryDiffList diffs;
484
485 public INodeDirectoryWithSnapshot(INodeDirectory that) {
486 this(that, true, that instanceof INodeDirectoryWithSnapshot?
487 ((INodeDirectoryWithSnapshot)that).getDiffs(): null);
488 }
489
490 INodeDirectoryWithSnapshot(INodeDirectory that, boolean adopt,
491 DirectoryDiffList diffs) {
492 super(that, adopt, that.getNsQuota(), that.getDsQuota());
493 this.diffs = diffs != null? diffs: new DirectoryDiffList();
494 }
495
496 /** @return the last snapshot. */
497 public Snapshot getLastSnapshot() {
498 return diffs.getLastSnapshot();
499 }
500
501 /** @return the snapshot diff list. */
502 public DirectoryDiffList getDiffs() {
503 return diffs;
504 }
505
506 @Override
507 public INodeDirectoryAttributes getSnapshotINode(Snapshot snapshot) {
508 return diffs.getSnapshotINode(snapshot, this);
509 }
510
511 @Override
512 public INodeDirectoryWithSnapshot recordModification(final Snapshot latest,
513 final INodeMap inodeMap) throws QuotaExceededException {
514 if (isInLatestSnapshot(latest) && !shouldRecordInSrcSnapshot(latest)) {
515 return saveSelf2Snapshot(latest, null);
516 }
517 return this;
518 }
519
520 /** Save the snapshot copy to the latest snapshot. */
521 public INodeDirectoryWithSnapshot saveSelf2Snapshot(
522 final Snapshot latest, final INodeDirectory snapshotCopy)
523 throws QuotaExceededException {
524 diffs.saveSelf2Snapshot(latest, this, snapshotCopy);
525 return this;
526 }
527
528 @Override
529 public INode saveChild2Snapshot(final INode child, final Snapshot latest,
530 final INode snapshotCopy, final INodeMap inodeMap)
531 throws QuotaExceededException {
532 Preconditions.checkArgument(!child.isDirectory(),
533 "child is a directory, child=%s", child);
534 if (latest == null) {
535 return child;
536 }
537
538 final DirectoryDiff diff = diffs.checkAndAddLatestSnapshotDiff(latest, this);
539 if (diff.getChild(child.getLocalNameBytes(), false, this) != null) {
540 // it was already saved in the latest snapshot earlier.
541 return child;
542 }
543
544 diff.diff.modify(snapshotCopy, child);
545 return child;
546 }
547
548 @Override
549 public boolean addChild(INode inode, boolean setModTime, Snapshot latest,
550 final INodeMap inodeMap) throws QuotaExceededException {
551 ChildrenDiff diff = null;
552 Integer undoInfo = null;
553 if (isInLatestSnapshot(latest)) {
554 diff = diffs.checkAndAddLatestSnapshotDiff(latest, this).diff;
555 undoInfo = diff.create(inode);
556 }
557 final boolean added = super.addChild(inode, setModTime, null, inodeMap);
558 if (!added && undoInfo != null) {
559 diff.undoCreate(inode, undoInfo);
560 }
561 return added;
562 }
563
564 @Override
565 public boolean removeChild(INode child, Snapshot latest,
566 final INodeMap inodeMap) throws QuotaExceededException {
567 ChildrenDiff diff = null;
568 UndoInfo<INode> undoInfo = null;
569 // For a directory that is not a renamed node, if isInLatestSnapshot returns
570 // false, the directory is not in the latest snapshot, thus we do not need
571 // to record the removed child in any snapshot.
572 // For a directory that was moved/renamed, note that if the directory is in
573 // any of the previous snapshots, we will create a reference node for the
574 // directory while rename, and isInLatestSnapshot will return true in that
575 // scenario (if all previous snapshots have been deleted, isInLatestSnapshot
576 // still returns false). Thus if isInLatestSnapshot returns false, the
577 // directory node cannot be in any snapshot (not in current tree, nor in
578 // previous src tree). Thus we do not need to record the removed child in
579 // any snapshot.
580 if (isInLatestSnapshot(latest)) {
581 diff = diffs.checkAndAddLatestSnapshotDiff(latest, this).diff;
582 undoInfo = diff.delete(child);
583 }
584 final boolean removed = removeChild(child);
585 if (undoInfo != null) {
586 if (!removed) {
587 //remove failed, undo
588 diff.undoDelete(child, undoInfo);
589 }
590 }
591 return removed;
592 }
593
594 @Override
595 public void replaceChild(final INode oldChild, final INode newChild,
596 final INodeMap inodeMap) {
597 super.replaceChild(oldChild, newChild, inodeMap);
598 diffs.replaceChild(ListType.CREATED, oldChild, newChild);
599 }
600
601 /**
602 * This method is usually called by the undo section of rename.
603 *
604 * Before calling this function, in the rename operation, we replace the
605 * original src node (of the rename operation) with a reference node (WithName
606 * instance) in both the children list and a created list, delete the
607 * reference node from the children list, and add it to the corresponding
608 * deleted list.
609 *
610 * To undo the above operations, we have the following steps in particular:
611 *
612 * <pre>
613 * 1) remove the WithName node from the deleted list (if it exists)
614 * 2) replace the WithName node in the created list with srcChild
615 * 3) add srcChild back as a child of srcParent. Note that we already add
616 * the node into the created list of a snapshot diff in step 2, we do not need
617 * to add srcChild to the created list of the latest snapshot.
618 * </pre>
619 *
620 * We do not need to update quota usage because the old child is in the
621 * deleted list before.
622 *
623 * @param oldChild
624 * The reference node to be removed/replaced
625 * @param newChild
626 * The node to be added back
627 * @param latestSnapshot
628 * The latest snapshot. Note this may not be the last snapshot in the
629 * {@link #diffs}, since the src tree of the current rename operation
630 * may be the dst tree of a previous rename.
631 * @throws QuotaExceededException should not throw this exception
632 */
633 public void undoRename4ScrParent(final INodeReference oldChild,
634 final INode newChild, Snapshot latestSnapshot)
635 throws QuotaExceededException {
636 diffs.removeChild(ListType.DELETED, oldChild);
637 diffs.replaceChild(ListType.CREATED, oldChild, newChild);
638 // pass null for inodeMap since the parent node will not get replaced when
639 // undoing rename
640 addChild(newChild, true, null, null);
641 }
642
643 /**
644 * Undo the rename operation for the dst tree, i.e., if the rename operation
645 * (with OVERWRITE option) removes a file/dir from the dst tree, add it back
646 * and delete possible record in the deleted list.
647 */
648 public void undoRename4DstParent(final INode deletedChild,
649 Snapshot latestSnapshot) throws QuotaExceededException {
650 boolean removeDeletedChild = diffs.removeChild(ListType.DELETED,
651 deletedChild);
652 // pass null for inodeMap since the parent node will not get replaced when
653 // undoing rename
654 final boolean added = addChild(deletedChild, true, removeDeletedChild ? null
655 : latestSnapshot, null);
656 // update quota usage if adding is successfully and the old child has not
657 // been stored in deleted list before
658 if (added && !removeDeletedChild) {
659 final Quota.Counts counts = deletedChild.computeQuotaUsage();
660 addSpaceConsumed(counts.get(Quota.NAMESPACE),
661 counts.get(Quota.DISKSPACE), false);
662 }
663 }
664
665 @Override
666 public ReadOnlyList<INode> getChildrenList(Snapshot snapshot) {
667 final DirectoryDiff diff = diffs.getDiff(snapshot);
668 return diff != null? diff.getChildrenList(this): super.getChildrenList(null);
669 }
670
671 @Override
672 public INode getChild(byte[] name, Snapshot snapshot) {
673 final DirectoryDiff diff = diffs.getDiff(snapshot);
674 return diff != null? diff.getChild(name, true, this): super.getChild(name, null);
675 }
676
677 @Override
678 public String toDetailString() {
679 return super.toDetailString() + ", " + diffs;
680 }
681
682 /**
683 * Get all the directories that are stored in some snapshot but not in the
684 * current children list. These directories are equivalent to the directories
685 * stored in the deletes lists.
686 */
687 public void getSnapshotDirectory(List<INodeDirectory> snapshotDir) {
688 for (DirectoryDiff sdiff : diffs) {
689 sdiff.getChildrenDiff().getDirsInDeleted(snapshotDir);
690 }
691 }
692
693 @Override
694 public Quota.Counts cleanSubtree(final Snapshot snapshot, Snapshot prior,
695 final BlocksMapUpdateInfo collectedBlocks,
696 final List<INode> removedINodes, final boolean countDiffChange)
697 throws QuotaExceededException {
698 Quota.Counts counts = Quota.Counts.newInstance();
699 Map<INode, INode> priorCreated = null;
700 Map<INode, INode> priorDeleted = null;
701 if (snapshot == null) { // delete the current directory
702 recordModification(prior, null);
703 // delete everything in created list
704 DirectoryDiff lastDiff = diffs.getLast();
705 if (lastDiff != null) {
706 counts.add(lastDiff.diff.destroyCreatedList(this, collectedBlocks,
707 removedINodes));
708 }
709 } else {
710 // update prior
711 prior = getDiffs().updatePrior(snapshot, prior);
712 // if there is a snapshot diff associated with prior, we need to record
713 // its original created and deleted list before deleting post
714 if (prior != null) {
715 DirectoryDiff priorDiff = this.getDiffs().getDiff(prior);
716 if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
717 List<INode> cList = priorDiff.diff.getList(ListType.CREATED);
718 List<INode> dList = priorDiff.diff.getList(ListType.DELETED);
719 priorCreated = new HashMap<INode, INode>(cList.size());
720 for (INode cNode : cList) {
721 priorCreated.put(cNode, cNode);
722 }
723 priorDeleted = new HashMap<INode, INode>(dList.size());
724 for (INode dNode : dList) {
725 priorDeleted.put(dNode, dNode);
726 }
727 }
728 }
729
730 counts.add(getDiffs().deleteSnapshotDiff(snapshot, prior, this,
731 collectedBlocks, removedINodes, countDiffChange));
732
733 // check priorDiff again since it may be created during the diff deletion
734 if (prior != null) {
735 DirectoryDiff priorDiff = this.getDiffs().getDiff(prior);
736 if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
737 // For files/directories created between "prior" and "snapshot",
738 // we need to clear snapshot copies for "snapshot". Note that we must
739 // use null as prior in the cleanSubtree call. Files/directories that
740 // were created before "prior" will be covered by the later
741 // cleanSubtreeRecursively call.
742 if (priorCreated != null) {
743 // we only check the node originally in prior's created list
744 for (INode cNode : priorDiff.getChildrenDiff().getList(
745 ListType.CREATED)) {
746 if (priorCreated.containsKey(cNode)) {
747 counts.add(cNode.cleanSubtree(snapshot, null, collectedBlocks,
748 removedINodes, countDiffChange));
749 }
750 }
751 }
752
753 // When a directory is moved from the deleted list of the posterior
754 // diff to the deleted list of this diff, we need to destroy its
755 // descendants that were 1) created after taking this diff and 2)
756 // deleted after taking posterior diff.
757
758 // For files moved from posterior's deleted list, we also need to
759 // delete its snapshot copy associated with the posterior snapshot.
760
761 for (INode dNode : priorDiff.getChildrenDiff().getList(
762 ListType.DELETED)) {
763 if (priorDeleted == null || !priorDeleted.containsKey(dNode)) {
764 counts.add(cleanDeletedINode(dNode, snapshot, prior,
765 collectedBlocks, removedINodes, countDiffChange));
766 }
767 }
768 }
769 }
770 }
771 counts.add(cleanSubtreeRecursively(snapshot, prior, collectedBlocks,
772 removedINodes, priorDeleted, countDiffChange));
773
774 if (isQuotaSet()) {
775 this.addSpaceConsumed2Cache(-counts.get(Quota.NAMESPACE),
776 -counts.get(Quota.DISKSPACE));
777 }
778 return counts;
779 }
780
781 /**
782 * Clean an inode while we move it from the deleted list of post to the
783 * deleted list of prior.
784 * @param inode The inode to clean.
785 * @param post The post snapshot.
786 * @param prior The prior snapshot.
787 * @param collectedBlocks Used to collect blocks for later deletion.
788 * @return Quota usage update.
789 */
790 private static Quota.Counts cleanDeletedINode(INode inode,
791 final Snapshot post, final Snapshot prior,
792 final BlocksMapUpdateInfo collectedBlocks,
793 final List<INode> removedINodes, final boolean countDiffChange)
794 throws QuotaExceededException {
795 Quota.Counts counts = Quota.Counts.newInstance();
796 Deque<INode> queue = new ArrayDeque<INode>();
797 queue.addLast(inode);
798 while (!queue.isEmpty()) {
799 INode topNode = queue.pollFirst();
800 if (topNode instanceof INodeReference.WithName) {
801 INodeReference.WithName wn = (INodeReference.WithName) topNode;
802 if (wn.getLastSnapshotId() >= post.getId()) {
803 wn.cleanSubtree(post, prior, collectedBlocks, removedINodes,
804 countDiffChange);
805 }
806 // For DstReference node, since the node is not in the created list of
807 // prior, we should treat it as regular file/dir
808 } else if (topNode.isFile()
809 && topNode.asFile() instanceof FileWithSnapshot) {
810 FileWithSnapshot fs = (FileWithSnapshot) topNode.asFile();
811 counts.add(fs.getDiffs().deleteSnapshotDiff(post, prior,
812 topNode.asFile(), collectedBlocks, removedINodes, countDiffChange));
813 } else if (topNode.isDirectory()) {
814 INodeDirectory dir = topNode.asDirectory();
815 ChildrenDiff priorChildrenDiff = null;
816 if (dir instanceof INodeDirectoryWithSnapshot) {
817 // delete files/dirs created after prior. Note that these
818 // files/dirs, along with inode, were deleted right after post.
819 INodeDirectoryWithSnapshot sdir = (INodeDirectoryWithSnapshot) dir;
820 DirectoryDiff priorDiff = sdir.getDiffs().getDiff(prior);
821 if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
822 priorChildrenDiff = priorDiff.getChildrenDiff();
823 counts.add(priorChildrenDiff.destroyCreatedList(sdir,
824 collectedBlocks, removedINodes));
825 }
826 }
827
828 for (INode child : dir.getChildrenList(prior)) {
829 if (priorChildrenDiff != null
830 && priorChildrenDiff.search(ListType.DELETED,
831 child.getLocalNameBytes()) != null) {
832 continue;
833 }
834 queue.addLast(child);
835 }
836 }
837 }
838 return counts;
839 }
840
841 @Override
842 public void destroyAndCollectBlocks(
843 final BlocksMapUpdateInfo collectedBlocks,
844 final List<INode> removedINodes) {
845 // destroy its diff list
846 for (DirectoryDiff diff : diffs) {
847 diff.destroyDiffAndCollectBlocks(this, collectedBlocks, removedINodes);
848 }
849 diffs.clear();
850 super.destroyAndCollectBlocks(collectedBlocks, removedINodes);
851 }
852
853 @Override
854 public final Quota.Counts computeQuotaUsage(Quota.Counts counts,
855 boolean useCache, int lastSnapshotId) {
856 if ((useCache && isQuotaSet()) || lastSnapshotId == Snapshot.INVALID_ID) {
857 return super.computeQuotaUsage(counts, useCache, lastSnapshotId);
858 }
859
860 Snapshot lastSnapshot = diffs.getSnapshotById(lastSnapshotId);
861
862 ReadOnlyList<INode> childrenList = getChildrenList(lastSnapshot);
863 for (INode child : childrenList) {
864 child.computeQuotaUsage(counts, useCache, lastSnapshotId);
865 }
866
867 counts.add(Quota.NAMESPACE, 1);
868 return counts;
869 }
870
871 @Override
872 public Quota.Counts computeQuotaUsage4CurrentDirectory(Quota.Counts counts) {
873 super.computeQuotaUsage4CurrentDirectory(counts);
874 for(DirectoryDiff d : diffs) {
875 for(INode deleted : d.getChildrenDiff().getList(ListType.DELETED)) {
876 deleted.computeQuotaUsage(counts, false, Snapshot.INVALID_ID);
877 }
878 }
879 counts.add(Quota.NAMESPACE, diffs.asList().size());
880 return counts;
881 }
882
883 @Override
884 public Content.Counts computeContentSummary(final Content.Counts counts) {
885 super.computeContentSummary(counts);
886 computeContentSummary4Snapshot(counts);
887 return counts;
888 }
889
890 private void computeContentSummary4Snapshot(final Content.Counts counts) {
891 for(DirectoryDiff d : diffs) {
892 for(INode deleted : d.getChildrenDiff().getList(ListType.DELETED)) {
893 deleted.computeContentSummary(counts);
894 }
895 }
896 counts.add(Content.DIRECTORY, diffs.asList().size());
897 }
898
899 /**
900 * Destroy a subtree under a DstReference node.
901 */
902 public static void destroyDstSubtree(INode inode, final Snapshot snapshot,
903 final Snapshot prior, final BlocksMapUpdateInfo collectedBlocks,
904 final List<INode> removedINodes) throws QuotaExceededException {
905 Preconditions.checkArgument(prior != null);
906 if (inode.isReference()) {
907 if (inode instanceof INodeReference.WithName && snapshot != null) {
908 // this inode has been renamed before the deletion of the DstReference
909 // subtree
910 inode.cleanSubtree(snapshot, prior, collectedBlocks, removedINodes,
911 true);
912 } else {
913 // for DstReference node, continue this process to its subtree
914 destroyDstSubtree(inode.asReference().getReferredINode(), snapshot,
915 prior, collectedBlocks, removedINodes);
916 }
917 } else if (inode.isFile() && snapshot != null) {
918 inode.cleanSubtree(snapshot, prior, collectedBlocks, removedINodes, true);
919 } else if (inode.isDirectory()) {
920 Map<INode, INode> excludedNodes = null;
921 if (inode instanceof INodeDirectoryWithSnapshot) {
922 INodeDirectoryWithSnapshot sdir = (INodeDirectoryWithSnapshot) inode;
923 DirectoryDiffList diffList = sdir.getDiffs();
924 if (snapshot != null) {
925 diffList.deleteSnapshotDiff(snapshot, prior, sdir, collectedBlocks,
926 removedINodes, true);
927 }
928 DirectoryDiff priorDiff = diffList.getDiff(prior);
929 if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
930 priorDiff.diff.destroyCreatedList(sdir, collectedBlocks,
931 removedINodes);
932 List<INode> dList = priorDiff.diff.getList(ListType.DELETED);
933 excludedNodes = new HashMap<INode, INode>(dList.size());
934 for (INode dNode : dList) {
935 excludedNodes.put(dNode, dNode);
936 }
937 }
938 }
939 for (INode child : inode.asDirectory().getChildrenList(prior)) {
940 if (excludedNodes != null && excludedNodes.containsKey(child)) {
941 continue;
942 }
943 destroyDstSubtree(child, snapshot, prior, collectedBlocks,
944 removedINodes);
945 }
946 }
947 }
948 }