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;
019
020 import java.io.PrintWriter;
021 import java.util.ArrayList;
022 import java.util.Collections;
023 import java.util.Comparator;
024 import java.util.List;
025
026 import org.apache.hadoop.fs.permission.FsPermission;
027 import org.apache.hadoop.fs.permission.PermissionStatus;
028 import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
029 import org.apache.hadoop.hdfs.server.namenode.snapshot.FileWithSnapshot;
030 import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot;
031 import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
032
033 import com.google.common.base.Preconditions;
034
035 /**
036 * An anonymous reference to an inode.
037 *
038 * This class and its subclasses are used to support multiple access paths.
039 * A file/directory may have multiple access paths when it is stored in some
040 * snapshots and it is renamed/moved to other locations.
041 *
042 * For example,
043 * (1) Support we have /abc/foo, say the inode of foo is inode(id=1000,name=foo)
044 * (2) create snapshot s0 for /abc
045 * (3) mv /abc/foo /xyz/bar, i.e. inode(id=1000,name=...) is renamed from "foo"
046 * to "bar" and its parent becomes /xyz.
047 *
048 * Then, /xyz/bar and /abc/.snapshot/s0/foo are two different access paths to
049 * the same inode, inode(id=1000,name=bar).
050 *
051 * With references, we have the following
052 * - /abc has a child ref(id=1001,name=foo).
053 * - /xyz has a child ref(id=1002)
054 * - Both ref(id=1001,name=foo) and ref(id=1002) point to another reference,
055 * ref(id=1003,count=2).
056 * - Finally, ref(id=1003,count=2) points to inode(id=1000,name=bar).
057 *
058 * Note 1: For a reference without name, e.g. ref(id=1002), it uses the name
059 * of the referred inode.
060 * Note 2: getParent() always returns the parent in the current state, e.g.
061 * inode(id=1000,name=bar).getParent() returns /xyz but not /abc.
062 */
063 public abstract class INodeReference extends INode {
064 /**
065 * Try to remove the given reference and then return the reference count.
066 * If the given inode is not a reference, return -1;
067 */
068 public static int tryRemoveReference(INode inode) {
069 if (!inode.isReference()) {
070 return -1;
071 }
072 return removeReference(inode.asReference());
073 }
074
075 /**
076 * Remove the given reference and then return the reference count.
077 * If the referred inode is not a WithCount, return -1;
078 */
079 private static int removeReference(INodeReference ref) {
080 final INode referred = ref.getReferredINode();
081 if (!(referred instanceof WithCount)) {
082 return -1;
083 }
084
085 WithCount wc = (WithCount) referred;
086 wc.removeReference(ref);
087 return wc.getReferenceCount();
088 }
089
090 /**
091 * When destroying a reference node (WithName or DstReference), we call this
092 * method to identify the snapshot which is the latest snapshot before the
093 * reference node's creation.
094 */
095 static Snapshot getPriorSnapshot(INodeReference ref) {
096 WithCount wc = (WithCount) ref.getReferredINode();
097 WithName wn = null;
098 if (ref instanceof DstReference) {
099 wn = wc.getLastWithName();
100 } else if (ref instanceof WithName) {
101 wn = wc.getPriorWithName((WithName) ref);
102 }
103 if (wn != null) {
104 INode referred = wc.getReferredINode();
105 if (referred instanceof FileWithSnapshot) {
106 return ((FileWithSnapshot) referred).getDiffs().getPrior(
107 wn.lastSnapshotId);
108 } else if (referred instanceof INodeDirectoryWithSnapshot) {
109 return ((INodeDirectoryWithSnapshot) referred).getDiffs().getPrior(
110 wn.lastSnapshotId);
111 }
112 }
113 return null;
114 }
115
116 private INode referred;
117
118 public INodeReference(INode parent, INode referred) {
119 super(parent);
120 this.referred = referred;
121 }
122
123 public final INode getReferredINode() {
124 return referred;
125 }
126
127 public final void setReferredINode(INode referred) {
128 this.referred = referred;
129 }
130
131 @Override
132 public final boolean isReference() {
133 return true;
134 }
135
136 @Override
137 public final INodeReference asReference() {
138 return this;
139 }
140
141 @Override
142 public final boolean isFile() {
143 return referred.isFile();
144 }
145
146 @Override
147 public final INodeFile asFile() {
148 return referred.asFile();
149 }
150
151 @Override
152 public final boolean isDirectory() {
153 return referred.isDirectory();
154 }
155
156 @Override
157 public final INodeDirectory asDirectory() {
158 return referred.asDirectory();
159 }
160
161 @Override
162 public final boolean isSymlink() {
163 return referred.isSymlink();
164 }
165
166 @Override
167 public final INodeSymlink asSymlink() {
168 return referred.asSymlink();
169 }
170
171 @Override
172 public byte[] getLocalNameBytes() {
173 return referred.getLocalNameBytes();
174 }
175
176 @Override
177 public void setLocalName(byte[] name) {
178 referred.setLocalName(name);
179 }
180
181 @Override
182 public final long getId() {
183 return referred.getId();
184 }
185
186 @Override
187 public final PermissionStatus getPermissionStatus(Snapshot snapshot) {
188 return referred.getPermissionStatus(snapshot);
189 }
190
191 @Override
192 public final String getUserName(Snapshot snapshot) {
193 return referred.getUserName(snapshot);
194 }
195
196 @Override
197 final void setUser(String user) {
198 referred.setUser(user);
199 }
200
201 @Override
202 public final String getGroupName(Snapshot snapshot) {
203 return referred.getGroupName(snapshot);
204 }
205
206 @Override
207 final void setGroup(String group) {
208 referred.setGroup(group);
209 }
210
211 @Override
212 public final FsPermission getFsPermission(Snapshot snapshot) {
213 return referred.getFsPermission(snapshot);
214 }
215 @Override
216 public final short getFsPermissionShort() {
217 return referred.getFsPermissionShort();
218 }
219
220 @Override
221 void setPermission(FsPermission permission) {
222 referred.setPermission(permission);
223 }
224
225 @Override
226 public long getPermissionLong() {
227 return referred.getPermissionLong();
228 }
229
230 @Override
231 public final long getModificationTime(Snapshot snapshot) {
232 return referred.getModificationTime(snapshot);
233 }
234
235 @Override
236 public final INode updateModificationTime(long mtime, Snapshot latest,
237 INodeMap inodeMap) throws QuotaExceededException {
238 return referred.updateModificationTime(mtime, latest, inodeMap);
239 }
240
241 @Override
242 public final void setModificationTime(long modificationTime) {
243 referred.setModificationTime(modificationTime);
244 }
245
246 @Override
247 public final long getAccessTime(Snapshot snapshot) {
248 return referred.getAccessTime(snapshot);
249 }
250
251 @Override
252 public final void setAccessTime(long accessTime) {
253 referred.setAccessTime(accessTime);
254 }
255
256 @Override
257 final INode recordModification(Snapshot latest, final INodeMap inodeMap)
258 throws QuotaExceededException {
259 referred.recordModification(latest, inodeMap);
260 // reference is never replaced
261 return this;
262 }
263
264 @Override // used by WithCount
265 public Quota.Counts cleanSubtree(Snapshot snapshot, Snapshot prior,
266 BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes,
267 final boolean countDiffChange) throws QuotaExceededException {
268 return referred.cleanSubtree(snapshot, prior, collectedBlocks,
269 removedINodes, countDiffChange);
270 }
271
272 @Override // used by WithCount
273 public void destroyAndCollectBlocks(
274 BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) {
275 if (removeReference(this) <= 0) {
276 referred.destroyAndCollectBlocks(collectedBlocks, removedINodes);
277 }
278 }
279
280 @Override
281 public Content.Counts computeContentSummary(Content.Counts counts) {
282 return referred.computeContentSummary(counts);
283 }
284
285 @Override
286 public Quota.Counts computeQuotaUsage(Quota.Counts counts, boolean useCache,
287 int lastSnapshotId) {
288 return referred.computeQuotaUsage(counts, useCache, lastSnapshotId);
289 }
290
291 @Override
292 public final INodeAttributes getSnapshotINode(Snapshot snapshot) {
293 return referred.getSnapshotINode(snapshot);
294 }
295
296 @Override
297 public final long getNsQuota() {
298 return referred.getNsQuota();
299 }
300
301 @Override
302 public final long getDsQuota() {
303 return referred.getDsQuota();
304 }
305
306 @Override
307 public final void clear() {
308 super.clear();
309 referred = null;
310 }
311
312 @Override
313 public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix,
314 final Snapshot snapshot) {
315 super.dumpTreeRecursively(out, prefix, snapshot);
316 if (this instanceof DstReference) {
317 out.print(", dstSnapshotId=" + ((DstReference) this).dstSnapshotId);
318 }
319 if (this instanceof WithCount) {
320 out.print(", count=" + ((WithCount)this).getReferenceCount());
321 }
322 out.println();
323
324 final StringBuilder b = new StringBuilder();
325 for(int i = 0; i < prefix.length(); i++) {
326 b.append(' ');
327 }
328 b.append("->");
329 getReferredINode().dumpTreeRecursively(out, b, snapshot);
330 }
331
332 public int getDstSnapshotId() {
333 return Snapshot.INVALID_ID;
334 }
335
336 /** An anonymous reference with reference count. */
337 public static class WithCount extends INodeReference {
338
339 private final List<WithName> withNameList = new ArrayList<WithName>();
340
341 /**
342 * Compare snapshot with IDs, where null indicates the current status thus
343 * is greater than any non-null snapshot.
344 */
345 public static final Comparator<WithName> WITHNAME_COMPARATOR
346 = new Comparator<WithName>() {
347 @Override
348 public int compare(WithName left, WithName right) {
349 return left.lastSnapshotId - right.lastSnapshotId;
350 }
351 };
352
353 public WithCount(INodeReference parent, INode referred) {
354 super(parent, referred);
355 Preconditions.checkArgument(!referred.isReference());
356 referred.setParentReference(this);
357 }
358
359 public int getReferenceCount() {
360 int count = withNameList.size();
361 if (getParentReference() != null) {
362 count++;
363 }
364 return count;
365 }
366
367 /** Increment and then return the reference count. */
368 public void addReference(INodeReference ref) {
369 if (ref instanceof WithName) {
370 WithName refWithName = (WithName) ref;
371 int i = Collections.binarySearch(withNameList, refWithName,
372 WITHNAME_COMPARATOR);
373 Preconditions.checkState(i < 0);
374 withNameList.add(-i - 1, refWithName);
375 } else if (ref instanceof DstReference) {
376 setParentReference(ref);
377 }
378 }
379
380 /** Decrement and then return the reference count. */
381 public void removeReference(INodeReference ref) {
382 if (ref instanceof WithName) {
383 int i = Collections.binarySearch(withNameList, (WithName) ref,
384 WITHNAME_COMPARATOR);
385 if (i >= 0) {
386 withNameList.remove(i);
387 }
388 } else if (ref == getParentReference()) {
389 setParent(null);
390 }
391 }
392
393 WithName getLastWithName() {
394 return withNameList.size() > 0 ?
395 withNameList.get(withNameList.size() - 1) : null;
396 }
397
398 WithName getPriorWithName(WithName post) {
399 int i = Collections.binarySearch(withNameList, post, WITHNAME_COMPARATOR);
400 if (i > 0) {
401 return withNameList.get(i - 1);
402 } else if (i == 0 || i == -1) {
403 return null;
404 } else {
405 return withNameList.get(-i - 2);
406 }
407 }
408 }
409
410 /** A reference with a fixed name. */
411 public static class WithName extends INodeReference {
412
413 private final byte[] name;
414
415 /**
416 * The id of the last snapshot in the src tree when this WithName node was
417 * generated. When calculating the quota usage of the referred node, only
418 * the files/dirs existing when this snapshot was taken will be counted for
419 * this WithName node and propagated along its ancestor path.
420 */
421 private final int lastSnapshotId;
422
423 public WithName(INodeDirectory parent, WithCount referred, byte[] name,
424 int lastSnapshotId) {
425 super(parent, referred);
426 this.name = name;
427 this.lastSnapshotId = lastSnapshotId;
428 referred.addReference(this);
429 }
430
431 @Override
432 public final byte[] getLocalNameBytes() {
433 return name;
434 }
435
436 @Override
437 public final void setLocalName(byte[] name) {
438 throw new UnsupportedOperationException("Cannot set name: " + getClass()
439 + " is immutable.");
440 }
441
442 public int getLastSnapshotId() {
443 return lastSnapshotId;
444 }
445
446 @Override
447 public final Content.Counts computeContentSummary(Content.Counts counts) {
448 //only count diskspace for WithName
449 final Quota.Counts q = Quota.Counts.newInstance();
450 computeQuotaUsage(q, false, lastSnapshotId);
451 counts.add(Content.DISKSPACE, q.get(Quota.DISKSPACE));
452 return counts;
453 }
454
455 @Override
456 public final Quota.Counts computeQuotaUsage(Quota.Counts counts,
457 boolean useCache, int lastSnapshotId) {
458 // if this.lastSnapshotId < lastSnapshotId, the rename of the referred
459 // node happened before the rename of its ancestor. This should be
460 // impossible since for WithName node we only count its children at the
461 // time of the rename.
462 Preconditions.checkState(this.lastSnapshotId >= lastSnapshotId);
463 final INode referred = this.getReferredINode().asReference()
464 .getReferredINode();
465 // We will continue the quota usage computation using the same snapshot id
466 // as time line (if the given snapshot id is valid). Also, we cannot use
467 // cache for the referred node since its cached quota may have already
468 // been updated by changes in the current tree.
469 int id = lastSnapshotId > Snapshot.INVALID_ID ?
470 lastSnapshotId : this.lastSnapshotId;
471 return referred.computeQuotaUsage(counts, false, id);
472 }
473
474 @Override
475 public Quota.Counts cleanSubtree(final Snapshot snapshot, Snapshot prior,
476 final BlocksMapUpdateInfo collectedBlocks,
477 final List<INode> removedINodes, final boolean countDiffChange)
478 throws QuotaExceededException {
479 // since WithName node resides in deleted list acting as a snapshot copy,
480 // the parameter snapshot must be non-null
481 Preconditions.checkArgument(snapshot != null);
482 // if prior is null, we need to check snapshot belonging to the previous
483 // WithName instance
484 if (prior == null) {
485 prior = getPriorSnapshot(this);
486 }
487
488 if (prior != null
489 && Snapshot.ID_COMPARATOR.compare(snapshot, prior) <= 0) {
490 return Quota.Counts.newInstance();
491 }
492
493 Quota.Counts counts = getReferredINode().cleanSubtree(snapshot, prior,
494 collectedBlocks, removedINodes, false);
495 INodeReference ref = getReferredINode().getParentReference();
496 if (ref != null) {
497 ref.addSpaceConsumed(-counts.get(Quota.NAMESPACE),
498 -counts.get(Quota.DISKSPACE), true);
499 }
500
501 if (snapshot.getId() < lastSnapshotId) {
502 // for a WithName node, when we compute its quota usage, we only count
503 // in all the nodes existing at the time of the corresponding rename op.
504 // Thus if we are deleting a snapshot before/at the snapshot associated
505 // with lastSnapshotId, we do not need to update the quota upwards.
506 counts = Quota.Counts.newInstance();
507 }
508 return counts;
509 }
510
511 @Override
512 public void destroyAndCollectBlocks(BlocksMapUpdateInfo collectedBlocks,
513 final List<INode> removedINodes) {
514 Snapshot snapshot = getSelfSnapshot();
515 if (removeReference(this) <= 0) {
516 getReferredINode().destroyAndCollectBlocks(collectedBlocks,
517 removedINodes);
518 } else {
519 Snapshot prior = getPriorSnapshot(this);
520 INode referred = getReferredINode().asReference().getReferredINode();
521
522 if (snapshot != null) {
523 if (prior != null && snapshot.getId() <= prior.getId()) {
524 // the snapshot to be deleted has been deleted while traversing
525 // the src tree of the previous rename operation. This usually
526 // happens when rename's src and dst are under the same
527 // snapshottable directory. E.g., the following operation sequence:
528 // 1. create snapshot s1 on /test
529 // 2. rename /test/foo/bar to /test/foo2/bar
530 // 3. create snapshot s2 on /test
531 // 4. rename foo2 again
532 // 5. delete snapshot s2
533 return;
534 }
535 try {
536 Quota.Counts counts = referred.cleanSubtree(snapshot, prior,
537 collectedBlocks, removedINodes, false);
538 INodeReference ref = getReferredINode().getParentReference();
539 if (ref != null) {
540 ref.addSpaceConsumed(-counts.get(Quota.NAMESPACE),
541 -counts.get(Quota.DISKSPACE), true);
542 }
543 } catch (QuotaExceededException e) {
544 LOG.error("should not exceed quota while snapshot deletion", e);
545 }
546 }
547 }
548 }
549
550 private Snapshot getSelfSnapshot() {
551 INode referred = getReferredINode().asReference().getReferredINode();
552 Snapshot snapshot = null;
553 if (referred instanceof FileWithSnapshot) {
554 snapshot = ((FileWithSnapshot) referred).getDiffs().getPrior(
555 lastSnapshotId);
556 } else if (referred instanceof INodeDirectoryWithSnapshot) {
557 snapshot = ((INodeDirectoryWithSnapshot) referred).getDiffs().getPrior(
558 lastSnapshotId);
559 }
560 return snapshot;
561 }
562 }
563
564 public static class DstReference extends INodeReference {
565 /**
566 * Record the latest snapshot of the dst subtree before the rename. For
567 * later operations on the moved/renamed files/directories, if the latest
568 * snapshot is after this dstSnapshot, changes will be recorded to the
569 * latest snapshot. Otherwise changes will be recorded to the snapshot
570 * belonging to the src of the rename.
571 *
572 * {@link Snapshot#INVALID_ID} means no dstSnapshot (e.g., src of the
573 * first-time rename).
574 */
575 private final int dstSnapshotId;
576
577 @Override
578 public final int getDstSnapshotId() {
579 return dstSnapshotId;
580 }
581
582 public DstReference(INodeDirectory parent, WithCount referred,
583 final int dstSnapshotId) {
584 super(parent, referred);
585 this.dstSnapshotId = dstSnapshotId;
586 referred.addReference(this);
587 }
588
589 @Override
590 public Quota.Counts cleanSubtree(Snapshot snapshot, Snapshot prior,
591 BlocksMapUpdateInfo collectedBlocks, List<INode> removedINodes,
592 final boolean countDiffChange) throws QuotaExceededException {
593 if (snapshot == null && prior == null) {
594 Quota.Counts counts = Quota.Counts.newInstance();
595 this.computeQuotaUsage(counts, true);
596 destroyAndCollectBlocks(collectedBlocks, removedINodes);
597 return counts;
598 } else {
599 // if prior is null, we need to check snapshot belonging to the previous
600 // WithName instance
601 if (prior == null) {
602 prior = getPriorSnapshot(this);
603 }
604 // if prior is not null, and prior is not before the to-be-deleted
605 // snapshot, we can quit here and leave the snapshot deletion work to
606 // the src tree of rename
607 if (snapshot != null && prior != null
608 && Snapshot.ID_COMPARATOR.compare(snapshot, prior) <= 0) {
609 return Quota.Counts.newInstance();
610 }
611 return getReferredINode().cleanSubtree(snapshot, prior,
612 collectedBlocks, removedINodes, countDiffChange);
613 }
614 }
615
616 /**
617 * {@inheritDoc}
618 * <br/>
619 * To destroy a DstReference node, we first remove its link with the
620 * referred node. If the reference number of the referred node is <= 0, we
621 * destroy the subtree of the referred node. Otherwise, we clean the
622 * referred node's subtree and delete everything created after the last
623 * rename operation, i.e., everything outside of the scope of the prior
624 * WithName nodes.
625 */
626 @Override
627 public void destroyAndCollectBlocks(
628 BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) {
629 if (removeReference(this) <= 0) {
630 getReferredINode().destroyAndCollectBlocks(collectedBlocks,
631 removedINodes);
632 } else {
633 // we will clean everything, including files, directories, and
634 // snapshots, that were created after this prior snapshot
635 Snapshot prior = getPriorSnapshot(this);
636 // prior must be non-null, otherwise we do not have any previous
637 // WithName nodes, and the reference number will be 0.
638 Preconditions.checkState(prior != null);
639 // identify the snapshot created after prior
640 Snapshot snapshot = getSelfSnapshot(prior);
641
642 INode referred = getReferredINode().asReference().getReferredINode();
643 if (referred instanceof FileWithSnapshot) {
644 // if referred is a file, it must be a FileWithSnapshot since we did
645 // recordModification before the rename
646 FileWithSnapshot sfile = (FileWithSnapshot) referred;
647 // make sure we mark the file as deleted
648 sfile.deleteCurrentFile();
649 if (snapshot != null) {
650 try {
651 // when calling cleanSubtree of the referred node, since we
652 // compute quota usage updates before calling this destroy
653 // function, we use true for countDiffChange
654 referred.cleanSubtree(snapshot, prior, collectedBlocks,
655 removedINodes, true);
656 } catch (QuotaExceededException e) {
657 LOG.error("should not exceed quota while snapshot deletion", e);
658 }
659 }
660 } else if (referred instanceof INodeDirectoryWithSnapshot) {
661 // similarly, if referred is a directory, it must be an
662 // INodeDirectoryWithSnapshot
663 INodeDirectoryWithSnapshot sdir =
664 (INodeDirectoryWithSnapshot) referred;
665 try {
666 INodeDirectoryWithSnapshot.destroyDstSubtree(sdir, snapshot, prior,
667 collectedBlocks, removedINodes);
668 } catch (QuotaExceededException e) {
669 LOG.error("should not exceed quota while snapshot deletion", e);
670 }
671 }
672 }
673 }
674
675 private Snapshot getSelfSnapshot(final Snapshot prior) {
676 WithCount wc = (WithCount) getReferredINode().asReference();
677 INode referred = wc.getReferredINode();
678 Snapshot lastSnapshot = null;
679 if (referred instanceof FileWithSnapshot) {
680 lastSnapshot = ((FileWithSnapshot) referred).getDiffs()
681 .getLastSnapshot();
682 } else if (referred instanceof INodeDirectoryWithSnapshot) {
683 lastSnapshot = ((INodeDirectoryWithSnapshot) referred)
684 .getLastSnapshot();
685 }
686 if (lastSnapshot != null && !lastSnapshot.equals(prior)) {
687 return lastSnapshot;
688 } else {
689 return null;
690 }
691 }
692 }
693 }