Package org.apache.hadoop.hdfs.server.namenode.snapshot

Source Code of org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot$DirectoryDiffList

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hdfs.server.namenode.snapshot;

import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffReportEntry;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffType;
import org.apache.hadoop.hdfs.server.namenode.Content;
import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization;
import org.apache.hadoop.hdfs.server.namenode.INode;
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
import org.apache.hadoop.hdfs.server.namenode.INodeDirectoryAttributes;
import org.apache.hadoop.hdfs.server.namenode.INodeDirectoryWithQuota;
import org.apache.hadoop.hdfs.server.namenode.INodeMap;
import org.apache.hadoop.hdfs.server.namenode.INodeReference;
import org.apache.hadoop.hdfs.server.namenode.Quota;
import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotFSImageFormat.ReferenceMap;
import org.apache.hadoop.hdfs.util.Diff;
import org.apache.hadoop.hdfs.util.Diff.Container;
import org.apache.hadoop.hdfs.util.Diff.ListType;
import org.apache.hadoop.hdfs.util.Diff.UndoInfo;
import org.apache.hadoop.hdfs.util.ReadOnlyList;

import com.google.common.base.Preconditions;

/**
* The directory with snapshots. It maintains a list of snapshot diffs for
* storing snapshot data. When there are modifications to the directory, the old
* data is stored in the latest snapshot, if there is any.
*/
public class INodeDirectoryWithSnapshot extends INodeDirectoryWithQuota {
  /**
   * The difference between the current state and a previous snapshot
   * of the children list of an INodeDirectory.
   */
  static class ChildrenDiff extends Diff<byte[], INode> {
    ChildrenDiff() {}
   
    private ChildrenDiff(final List<INode> created, final List<INode> deleted) {
      super(created, deleted);
    }

    /**
     * Replace the given child from the created/deleted list.
     * @return true if the child is replaced; false if the child is not found.
     */
    private final boolean replace(final ListType type,
        final INode oldChild, final INode newChild) {
      final List<INode> list = getList(type);
      final int i = search(list, oldChild.getLocalNameBytes());
      if (i < 0) {
        return false;
      }

      final INode removed = list.set(i, newChild);
      Preconditions.checkState(removed == oldChild);
      return true;
    }

    private final boolean removeChild(ListType type, final INode child) {
      final List<INode> list = getList(type);
      final int i = searchIndex(type, child.getLocalNameBytes());
      if (i >= 0 && list.get(i) == child) {
        list.remove(i);
        return true;
      }
      return false;
    }
   
    /** clear the created list */
    private Quota.Counts destroyCreatedList(
        final INodeDirectoryWithSnapshot currentINode,
        final BlocksMapUpdateInfo collectedBlocks,
        final List<INode> removedINodes) {
      Quota.Counts counts = Quota.Counts.newInstance();
      final List<INode> createdList = getList(ListType.CREATED);
      for (INode c : createdList) {
        c.computeQuotaUsage(counts, true);
        c.destroyAndCollectBlocks(collectedBlocks, removedINodes);
        // c should be contained in the children list, remove it
        currentINode.removeChild(c);
      }
      createdList.clear();
      return counts;
    }
   
    /** clear the deleted list */
    private Quota.Counts destroyDeletedList(
        final BlocksMapUpdateInfo collectedBlocks,
        final List<INode> removedINodes) {
      Quota.Counts counts = Quota.Counts.newInstance();
      final List<INode> deletedList = getList(ListType.DELETED);
      for (INode d : deletedList) {
        d.computeQuotaUsage(counts, false);
        d.destroyAndCollectBlocks(collectedBlocks, removedINodes);
      }
      deletedList.clear();
      return counts;
    }
   
    /** Serialize {@link #created} */
    private void writeCreated(DataOutput out) throws IOException {
      final List<INode> created = getList(ListType.CREATED);
      out.writeInt(created.size());
      for (INode node : created) {
        // For INode in created list, we only need to record its local name
        byte[] name = node.getLocalNameBytes();
        out.writeShort(name.length);
        out.write(name);
      }
    }
   
    /** Serialize {@link #deleted} */
    private void writeDeleted(DataOutput out,
        ReferenceMap referenceMap) throws IOException {
      final List<INode> deleted = getList(ListType.DELETED);
      out.writeInt(deleted.size());
      for (INode node : deleted) {
        FSImageSerialization.saveINode2Image(node, out, true, referenceMap);
      }
    }
   
    /** Serialize to out */
    private void write(DataOutput out, ReferenceMap referenceMap
        ) throws IOException {
      writeCreated(out);
      writeDeleted(out, referenceMap);   
    }

    /** Get the list of INodeDirectory contained in the deleted list */
    private void getDirsInDeleted(List<INodeDirectory> dirList) {
      for (INode node : getList(ListType.DELETED)) {
        if (node.isDirectory()) {
          dirList.add(node.asDirectory());
        }
      }
    }
   
    /**
     * Interpret the diff and generate a list of {@link DiffReportEntry}.
     * @param parentPath The relative path of the parent.
     * @param parent The directory that the diff belongs to.
     * @param fromEarlier True indicates {@code diff=later-earlier},
     *                    False indicates {@code diff=earlier-later}
     * @return A list of {@link DiffReportEntry} as the diff report.
     */
    public List<DiffReportEntry> generateReport(byte[][] parentPath,
        INodeDirectoryWithSnapshot parent, boolean fromEarlier) {
      List<DiffReportEntry> cList = new ArrayList<DiffReportEntry>();
      List<DiffReportEntry> dList = new ArrayList<DiffReportEntry>();
      int c = 0, d = 0;
      List<INode> created = getList(ListType.CREATED);
      List<INode> deleted = getList(ListType.DELETED);
      byte[][] fullPath = new byte[parentPath.length + 1][];
      System.arraycopy(parentPath, 0, fullPath, 0, parentPath.length);
      for (; c < created.size() && d < deleted.size(); ) {
        INode cnode = created.get(c);
        INode dnode = deleted.get(d);
        if (cnode.compareTo(dnode.getLocalNameBytes()) == 0) {
          fullPath[fullPath.length - 1] = cnode.getLocalNameBytes();
          if (cnode.isSymlink() && dnode.isSymlink()) {
            dList.add(new DiffReportEntry(DiffType.MODIFY, fullPath));
          } else {
            // must be the case: delete first and then create an inode with the
            // same name
            cList.add(new DiffReportEntry(DiffType.CREATE, fullPath));
            dList.add(new DiffReportEntry(DiffType.DELETE, fullPath));
          }
          c++;
          d++;
        } else if (cnode.compareTo(dnode.getLocalNameBytes()) < 0) {
          fullPath[fullPath.length - 1] = cnode.getLocalNameBytes();
          cList.add(new DiffReportEntry(fromEarlier ? DiffType.CREATE
              : DiffType.DELETE, fullPath));
          c++;
        } else {
          fullPath[fullPath.length - 1] = dnode.getLocalNameBytes();
          dList.add(new DiffReportEntry(fromEarlier ? DiffType.DELETE
              : DiffType.CREATE, fullPath));
          d++;
        }
      }
      for (; d < deleted.size(); d++) {
        fullPath[fullPath.length - 1] = deleted.get(d).getLocalNameBytes();
        dList.add(new DiffReportEntry(fromEarlier ? DiffType.DELETE
            : DiffType.CREATE, fullPath));
      }
      for (; c < created.size(); c++) {
        fullPath[fullPath.length - 1] = created.get(c).getLocalNameBytes();
        cList.add(new DiffReportEntry(fromEarlier ? DiffType.CREATE
            : DiffType.DELETE, fullPath));
      }
      dList.addAll(cList);
      return dList;
    }
  }
 
  /**
   * The difference of an {@link INodeDirectory} between two snapshots.
   */
  public static class DirectoryDiff extends
      AbstractINodeDiff<INodeDirectory, INodeDirectoryAttributes, DirectoryDiff> {
    /** The size of the children list at snapshot creation time. */
    private final int childrenSize;
    /** The children list diff. */
    private final ChildrenDiff diff;

    private DirectoryDiff(Snapshot snapshot, INodeDirectory dir) {
      super(snapshot, null, null);

      this.childrenSize = dir.getChildrenList(null).size();
      this.diff = new ChildrenDiff();
    }

    /** Constructor used by FSImage loading */
    DirectoryDiff(Snapshot snapshot, INodeDirectoryAttributes snapshotINode,
        DirectoryDiff posteriorDiff, int childrenSize,
        List<INode> createdList, List<INode> deletedList) {
      super(snapshot, snapshotINode, posteriorDiff);
      this.childrenSize = childrenSize;
      this.diff = new ChildrenDiff(createdList, deletedList);
    }
   
    ChildrenDiff getChildrenDiff() {
      return diff;
    }
   
    /** Is the inode the root of the snapshot? */
    boolean isSnapshotRoot() {
      return snapshotINode == snapshot.getRoot();
    }
   
    @Override
    Quota.Counts combinePosteriorAndCollectBlocks(
        final INodeDirectory currentDir, final DirectoryDiff posterior,
        final BlocksMapUpdateInfo collectedBlocks,
        final List<INode> removedINodes) {
      final Quota.Counts counts = Quota.Counts.newInstance();
      diff.combinePosterior(posterior.diff, new Diff.Processor<INode>() {
        /** Collect blocks for deleted files. */
        @Override
        public void process(INode inode) {
          if (inode != null) {
            inode.computeQuotaUsage(counts, false);
            inode.destroyAndCollectBlocks(collectedBlocks, removedINodes);
          }
        }
      });
      return counts;
    }

    /**
     * @return The children list of a directory in a snapshot.
     *         Since the snapshot is read-only, the logical view of the list is
     *         never changed although the internal data structure may mutate.
     */
    ReadOnlyList<INode> getChildrenList(final INodeDirectory currentDir) {
      return new ReadOnlyList<INode>() {
        private List<INode> children = null;

        private List<INode> initChildren() {
          if (children == null) {
            final ChildrenDiff combined = new ChildrenDiff();
            for(DirectoryDiff d = DirectoryDiff.this; d != null; d = d.getPosterior()) {
              combined.combinePosterior(d.diff, null);
            }
            children = combined.apply2Current(ReadOnlyList.Util.asList(
                currentDir.getChildrenList(null)));
          }
          return children;
        }

        @Override
        public Iterator<INode> iterator() {
          return initChildren().iterator();
        }
   
        @Override
        public boolean isEmpty() {
          return childrenSize == 0;
        }
   
        @Override
        public int size() {
          return childrenSize;
        }
   
        @Override
        public INode get(int i) {
          return initChildren().get(i);
        }
      };
    }

    /** @return the child with the given name. */
    INode getChild(byte[] name, boolean checkPosterior,
        INodeDirectory currentDir) {
      for(DirectoryDiff d = this; ; d = d.getPosterior()) {
        final Container<INode> returned = d.diff.accessPrevious(name);
        if (returned != null) {
          // the diff is able to determine the inode
          return returned.getElement();
        } else if (!checkPosterior) {
          // Since checkPosterior is false, return null, i.e. not found.  
          return null;
        } else if (d.getPosterior() == null) {
          // no more posterior diff, get from current inode.
          return currentDir.getChild(name, null);
        }
      }
    }
   
    @Override
    public String toString() {
      return super.toString() + " childrenSize=" + childrenSize + ", " + diff;
    }
   
    @Override
    void write(DataOutput out, ReferenceMap referenceMap) throws IOException {
      writeSnapshot(out);
      out.writeInt(childrenSize);

      // write snapshotINode
      if (isSnapshotRoot()) {
        out.writeBoolean(true);
      } else {
        out.writeBoolean(false);
        if (snapshotINode != null) {
          out.writeBoolean(true);
          FSImageSerialization.writeINodeDirectoryAttributes(snapshotINode, out);
        } else {
          out.writeBoolean(false);
        }
      }
      // Write diff. Node need to write poseriorDiff, since diffs is a list.
      diff.write(out, referenceMap);
    }

    @Override
    Quota.Counts destroyDiffAndCollectBlocks(INodeDirectory currentINode,
        BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) {
      // this diff has been deleted
      Quota.Counts counts = Quota.Counts.newInstance();
      counts.add(diff.destroyDeletedList(collectedBlocks, removedINodes));
      return counts;
    }
  }

  /** A list of directory diffs. */
  public static class DirectoryDiffList
      extends AbstractINodeDiffList<INodeDirectory, INodeDirectoryAttributes, DirectoryDiff> {

    @Override
    DirectoryDiff createDiff(Snapshot snapshot, INodeDirectory currentDir) {
      return new DirectoryDiff(snapshot, currentDir);
    }

    @Override
    INodeDirectoryAttributes createSnapshotCopy(INodeDirectory currentDir) {
      return currentDir.isQuotaSet()?
          new INodeDirectoryAttributes.CopyWithQuota(currentDir)
        : new INodeDirectoryAttributes.SnapshotCopy(currentDir);
    }

    /** Replace the given child in the created/deleted list, if there is any. */
    private boolean replaceChild(final ListType type, final INode oldChild,
        final INode newChild) {
      final List<DirectoryDiff> diffList = asList();
      for(int i = diffList.size() - 1; i >= 0; i--) {
        final ChildrenDiff diff = diffList.get(i).diff;
        if (diff.replace(type, oldChild, newChild)) {
          return true;
        }
      }
      return false;
    }
   
    /** Remove the given child in the created/deleted list, if there is any. */
    private boolean removeChild(final ListType type, final INode child) {
      final List<DirectoryDiff> diffList = asList();
      for(int i = diffList.size() - 1; i >= 0; i--) {
        final ChildrenDiff diff = diffList.get(i).diff;
        if (diff.removeChild(type, child)) {
          return true;
        }
      }
      return false;
    }
  }

  /**
   * Compute the difference between Snapshots.
   *
   * @param fromSnapshot Start point of the diff computation. Null indicates
   *          current tree.
   * @param toSnapshot End point of the diff computation. Null indicates current
   *          tree.
   * @param diff Used to capture the changes happening to the children. Note
   *          that the diff still represents (later_snapshot - earlier_snapshot)
   *          although toSnapshot can be before fromSnapshot.
   * @return Whether changes happened between the startSnapshot and endSnaphsot.
   */
  boolean computeDiffBetweenSnapshots(Snapshot fromSnapshot,
      Snapshot toSnapshot, ChildrenDiff diff) {
    Snapshot earlier = fromSnapshot;
    Snapshot later = toSnapshot;
    if (Snapshot.ID_COMPARATOR.compare(fromSnapshot, toSnapshot) > 0) {
      earlier = toSnapshot;
      later = fromSnapshot;
    }
   
    boolean modified = diffs.changedBetweenSnapshots(earlier,
        later);
    if (!modified) {
      return false;
    }
   
    final List<DirectoryDiff> difflist = diffs.asList();
    final int size = difflist.size();
    int earlierDiffIndex = Collections.binarySearch(difflist, earlier.getId());
    int laterDiffIndex = later == null ? size : Collections
        .binarySearch(difflist, later.getId());
    earlierDiffIndex = earlierDiffIndex < 0 ? (-earlierDiffIndex - 1)
        : earlierDiffIndex;
    laterDiffIndex = laterDiffIndex < 0 ? (-laterDiffIndex - 1)
        : laterDiffIndex;
   
    boolean dirMetadataChanged = false;
    INodeDirectoryAttributes dirCopy = null;
    for (int i = earlierDiffIndex; i < laterDiffIndex; i++) {
      DirectoryDiff sdiff = difflist.get(i);
      diff.combinePosterior(sdiff.diff, null);
      if (dirMetadataChanged == false && sdiff.snapshotINode != null) {
        if (dirCopy == null) {
          dirCopy = sdiff.snapshotINode;
        } else if (!dirCopy.metadataEquals(sdiff.snapshotINode)) {
          dirMetadataChanged = true;
        }
      }
    }

    if (!diff.isEmpty() || dirMetadataChanged) {
      return true;
    } else if (dirCopy != null) {
      for (int i = laterDiffIndex; i < size; i++) {
        if (!dirCopy.metadataEquals(difflist.get(i).snapshotINode)) {
          return true;
        }
      }
      return !dirCopy.metadataEquals(this);
    } else {
      return false;
    }
  }

  /** Diff list sorted by snapshot IDs, i.e. in chronological order. */
  private final DirectoryDiffList diffs;

  public INodeDirectoryWithSnapshot(INodeDirectory that) {
    this(that, true, that instanceof INodeDirectoryWithSnapshot?
        ((INodeDirectoryWithSnapshot)that).getDiffs(): null);
  }

  INodeDirectoryWithSnapshot(INodeDirectory that, boolean adopt,
      DirectoryDiffList diffs) {
    super(that, adopt, that.getNsQuota(), that.getDsQuota());
    this.diffs = diffs != null? diffs: new DirectoryDiffList();
  }

  /** @return the last snapshot. */
  public Snapshot getLastSnapshot() {
    return diffs.getLastSnapshot();
  }

  /** @return the snapshot diff list. */
  public DirectoryDiffList getDiffs() {
    return diffs;
  }

  @Override
  public INodeDirectoryAttributes getSnapshotINode(Snapshot snapshot) {
    return diffs.getSnapshotINode(snapshot, this);
  }

  @Override
  public INodeDirectoryWithSnapshot recordModification(final Snapshot latest,
      final INodeMap inodeMap) throws QuotaExceededException {
    if (isInLatestSnapshot(latest) && !shouldRecordInSrcSnapshot(latest)) {
      return saveSelf2Snapshot(latest, null);
    }
    return this;
  }

  /** Save the snapshot copy to the latest snapshot. */
  public INodeDirectoryWithSnapshot saveSelf2Snapshot(
      final Snapshot latest, final INodeDirectory snapshotCopy)
          throws QuotaExceededException {
    diffs.saveSelf2Snapshot(latest, this, snapshotCopy);
    return this;
  }

  @Override
  public INode saveChild2Snapshot(final INode child, final Snapshot latest,
      final INode snapshotCopy, final INodeMap inodeMap)
      throws QuotaExceededException {
    Preconditions.checkArgument(!child.isDirectory(),
        "child is a directory, child=%s", child);
    if (latest == null) {
      return child;
    }

    final DirectoryDiff diff = diffs.checkAndAddLatestSnapshotDiff(latest, this);
    if (diff.getChild(child.getLocalNameBytes(), false, this) != null) {
      // it was already saved in the latest snapshot earlier. 
      return child;
    }

    diff.diff.modify(snapshotCopy, child);
    return child;
  }

  @Override
  public boolean addChild(INode inode, boolean setModTime, Snapshot latest,
      final INodeMap inodeMap) throws QuotaExceededException {
    ChildrenDiff diff = null;
    Integer undoInfo = null;
    if (isInLatestSnapshot(latest)) {
      diff = diffs.checkAndAddLatestSnapshotDiff(latest, this).diff;
      undoInfo = diff.create(inode);
    }
    final boolean added = super.addChild(inode, setModTime, null, inodeMap);
    if (!added && undoInfo != null) {
      diff.undoCreate(inode, undoInfo);
    }
    return added;
  }

  @Override
  public boolean removeChild(INode child, Snapshot latest,
      final INodeMap inodeMap) throws QuotaExceededException {
    ChildrenDiff diff = null;
    UndoInfo<INode> undoInfo = null;
    // For a directory that is not a renamed node, if isInLatestSnapshot returns
    // false, the directory is not in the latest snapshot, thus we do not need
    // to record the removed child in any snapshot.
    // For a directory that was moved/renamed, note that if the directory is in
    // any of the previous snapshots, we will create a reference node for the
    // directory while rename, and isInLatestSnapshot will return true in that
    // scenario (if all previous snapshots have been deleted, isInLatestSnapshot
    // still returns false). Thus if isInLatestSnapshot returns false, the
    // directory node cannot be in any snapshot (not in current tree, nor in
    // previous src tree). Thus we do not need to record the removed child in
    // any snapshot.
    if (isInLatestSnapshot(latest)) {
      diff = diffs.checkAndAddLatestSnapshotDiff(latest, this).diff;
      undoInfo = diff.delete(child);
    }
    final boolean removed = removeChild(child);
    if (undoInfo != null) {
      if (!removed) {
        //remove failed, undo
        diff.undoDelete(child, undoInfo);
      }
    }
    return removed;
  }
 
  @Override
  public void replaceChild(final INode oldChild, final INode newChild,
      final INodeMap inodeMap) {
    super.replaceChild(oldChild, newChild, inodeMap);
    diffs.replaceChild(ListType.CREATED, oldChild, newChild);
  }
 
  /**
   * This method is usually called by the undo section of rename.
   *
   * Before calling this function, in the rename operation, we replace the
   * original src node (of the rename operation) with a reference node (WithName
   * instance) in both the children list and a created list, delete the
   * reference node from the children list, and add it to the corresponding
   * deleted list.
   *
   * To undo the above operations, we have the following steps in particular:
   *
   * <pre>
   * 1) remove the WithName node from the deleted list (if it exists)
   * 2) replace the WithName node in the created list with srcChild
   * 3) add srcChild back as a child of srcParent. Note that we already add
   * the node into the created list of a snapshot diff in step 2, we do not need
   * to add srcChild to the created list of the latest snapshot.
   * </pre>
   *
   * We do not need to update quota usage because the old child is in the
   * deleted list before.
   *
   * @param oldChild
   *          The reference node to be removed/replaced
   * @param newChild
   *          The node to be added back
   * @param latestSnapshot
   *          The latest snapshot. Note this may not be the last snapshot in the
   *          {@link #diffs}, since the src tree of the current rename operation
   *          may be the dst tree of a previous rename.
   * @throws QuotaExceededException should not throw this exception
   */
  public void undoRename4ScrParent(final INodeReference oldChild,
      final INode newChild, Snapshot latestSnapshot)
      throws QuotaExceededException {
    diffs.removeChild(ListType.DELETED, oldChild);
    diffs.replaceChild(ListType.CREATED, oldChild, newChild);
    // pass null for inodeMap since the parent node will not get replaced when
    // undoing rename
    addChild(newChild, true, null, null);
  }
 
  /**
   * Undo the rename operation for the dst tree, i.e., if the rename operation
   * (with OVERWRITE option) removes a file/dir from the dst tree, add it back
   * and delete possible record in the deleted list. 
   */
  public void undoRename4DstParent(final INode deletedChild,
      Snapshot latestSnapshot) throws QuotaExceededException {
    boolean removeDeletedChild = diffs.removeChild(ListType.DELETED,
        deletedChild);
    // pass null for inodeMap since the parent node will not get replaced when
    // undoing rename
    final boolean added = addChild(deletedChild, true, removeDeletedChild ? null
        : latestSnapshot, null);
    // update quota usage if adding is successfully and the old child has not
    // been stored in deleted list before
    if (added && !removeDeletedChild) {
      final Quota.Counts counts = deletedChild.computeQuotaUsage();
      addSpaceConsumed(counts.get(Quota.NAMESPACE),
          counts.get(Quota.DISKSPACE), false);
    }
  }

  @Override
  public ReadOnlyList<INode> getChildrenList(Snapshot snapshot) {
    final DirectoryDiff diff = diffs.getDiff(snapshot);
    return diff != null? diff.getChildrenList(this): super.getChildrenList(null);
  }

  @Override
  public INode getChild(byte[] name, Snapshot snapshot) {
    final DirectoryDiff diff = diffs.getDiff(snapshot);
    return diff != null? diff.getChild(name, true, this): super.getChild(name, null);
  }

  @Override
  public String toDetailString() {
    return super.toDetailString() + ", " + diffs;
  }
 
  /**
   * Get all the directories that are stored in some snapshot but not in the
   * current children list. These directories are equivalent to the directories
   * stored in the deletes lists.
   */
  public void getSnapshotDirectory(List<INodeDirectory> snapshotDir) {
    for (DirectoryDiff sdiff : diffs) {
      sdiff.getChildrenDiff().getDirsInDeleted(snapshotDir);
    }
  }

  @Override
  public Quota.Counts cleanSubtree(final Snapshot snapshot, Snapshot prior,
      final BlocksMapUpdateInfo collectedBlocks,
      final List<INode> removedINodes, final boolean countDiffChange)
      throws QuotaExceededException {
    Quota.Counts counts = Quota.Counts.newInstance();
    Map<INode, INode> priorCreated = null;
    Map<INode, INode> priorDeleted = null;
    if (snapshot == null) { // delete the current directory
      recordModification(prior, null);
      // delete everything in created list
      DirectoryDiff lastDiff = diffs.getLast();
      if (lastDiff != null) {
        counts.add(lastDiff.diff.destroyCreatedList(this, collectedBlocks,
            removedINodes));
      }
    } else {
      // update prior
      prior = getDiffs().updatePrior(snapshot, prior);
      // if there is a snapshot diff associated with prior, we need to record
      // its original created and deleted list before deleting post
      if (prior != null) {
        DirectoryDiff priorDiff = this.getDiffs().getDiff(prior);
        if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
          List<INode> cList = priorDiff.diff.getList(ListType.CREATED);
          List<INode> dList = priorDiff.diff.getList(ListType.DELETED);
          priorCreated = new HashMap<INode, INode>(cList.size());
          for (INode cNode : cList) {
            priorCreated.put(cNode, cNode);
          }
          priorDeleted = new HashMap<INode, INode>(dList.size());
          for (INode dNode : dList) {
            priorDeleted.put(dNode, dNode);
          }
        }
      }
     
      counts.add(getDiffs().deleteSnapshotDiff(snapshot, prior, this,
          collectedBlocks, removedINodes, countDiffChange));
     
      // check priorDiff again since it may be created during the diff deletion
      if (prior != null) {
        DirectoryDiff priorDiff = this.getDiffs().getDiff(prior);
        if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
          // For files/directories created between "prior" and "snapshot",
          // we need to clear snapshot copies for "snapshot". Note that we must
          // use null as prior in the cleanSubtree call. Files/directories that
          // were created before "prior" will be covered by the later
          // cleanSubtreeRecursively call.
          if (priorCreated != null) {
            // we only check the node originally in prior's created list
            for (INode cNode : priorDiff.getChildrenDiff().getList(
                ListType.CREATED)) {
              if (priorCreated.containsKey(cNode)) {
                counts.add(cNode.cleanSubtree(snapshot, null, collectedBlocks,
                    removedINodes, countDiffChange));
              }
            }
          }
         
          // When a directory is moved from the deleted list of the posterior
          // diff to the deleted list of this diff, we need to destroy its
          // descendants that were 1) created after taking this diff and 2)
          // deleted after taking posterior diff.

          // For files moved from posterior's deleted list, we also need to
          // delete its snapshot copy associated with the posterior snapshot.
         
          for (INode dNode : priorDiff.getChildrenDiff().getList(
              ListType.DELETED)) {
            if (priorDeleted == null || !priorDeleted.containsKey(dNode)) {
              counts.add(cleanDeletedINode(dNode, snapshot, prior,
                  collectedBlocks, removedINodes, countDiffChange));
            }
          }
        }
      }
    }
    counts.add(cleanSubtreeRecursively(snapshot, prior, collectedBlocks,
        removedINodes, priorDeleted, countDiffChange));
   
    if (isQuotaSet()) {
      this.addSpaceConsumed2Cache(-counts.get(Quota.NAMESPACE),
          -counts.get(Quota.DISKSPACE));
    }
    return counts;
  }
 
  /**
   * Clean an inode while we move it from the deleted list of post to the
   * deleted list of prior.
   * @param inode The inode to clean.
   * @param post The post snapshot.
   * @param prior The prior snapshot.
   * @param collectedBlocks Used to collect blocks for later deletion.
   * @return Quota usage update.
   */
  private static Quota.Counts cleanDeletedINode(INode inode,
      final Snapshot post, final Snapshot prior,
      final BlocksMapUpdateInfo collectedBlocks,
      final List<INode> removedINodes, final boolean countDiffChange)
      throws QuotaExceededException {
    Quota.Counts counts = Quota.Counts.newInstance();
    Deque<INode> queue = new ArrayDeque<INode>();
    queue.addLast(inode);
    while (!queue.isEmpty()) {
      INode topNode = queue.pollFirst();
      if (topNode instanceof INodeReference.WithName) {
        INodeReference.WithName wn = (INodeReference.WithName) topNode;
        if (wn.getLastSnapshotId() >= post.getId()) {
          wn.cleanSubtree(post, prior, collectedBlocks, removedINodes,
              countDiffChange);
        }
        // For DstReference node, since the node is not in the created list of
        // prior, we should treat it as regular file/dir
      } else if (topNode.isFile()
          && topNode.asFile() instanceof FileWithSnapshot) {
        FileWithSnapshot fs = (FileWithSnapshot) topNode.asFile();
        counts.add(fs.getDiffs().deleteSnapshotDiff(post, prior,
            topNode.asFile(), collectedBlocks, removedINodes, countDiffChange));
      } else if (topNode.isDirectory()) {
        INodeDirectory dir = topNode.asDirectory();
        ChildrenDiff priorChildrenDiff = null;
        if (dir instanceof INodeDirectoryWithSnapshot) {
          // delete files/dirs created after prior. Note that these
          // files/dirs, along with inode, were deleted right after post.
          INodeDirectoryWithSnapshot sdir = (INodeDirectoryWithSnapshot) dir;
          DirectoryDiff priorDiff = sdir.getDiffs().getDiff(prior);
          if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
            priorChildrenDiff = priorDiff.getChildrenDiff();
            counts.add(priorChildrenDiff.destroyCreatedList(sdir,
                collectedBlocks, removedINodes));
          }
        }
       
        for (INode child : dir.getChildrenList(prior)) {
          if (priorChildrenDiff != null
              && priorChildrenDiff.search(ListType.DELETED,
                  child.getLocalNameBytes()) != null) {
            continue;
          }
          queue.addLast(child);
        }
      }
    }
    return counts;
  }

  @Override
  public void destroyAndCollectBlocks(
      final BlocksMapUpdateInfo collectedBlocks,
      final List<INode> removedINodes) {
    // destroy its diff list
    for (DirectoryDiff diff : diffs) {
      diff.destroyDiffAndCollectBlocks(this, collectedBlocks, removedINodes);
    }
    diffs.clear();
    super.destroyAndCollectBlocks(collectedBlocks, removedINodes);
  }

  @Override
  public final Quota.Counts computeQuotaUsage(Quota.Counts counts,
      boolean useCache, int lastSnapshotId) {
    if ((useCache && isQuotaSet()) || lastSnapshotId == Snapshot.INVALID_ID) {
      return super.computeQuotaUsage(counts, useCache, lastSnapshotId);
    }
   
    Snapshot lastSnapshot = diffs.getSnapshotById(lastSnapshotId);
   
    ReadOnlyList<INode> childrenList = getChildrenList(lastSnapshot);
    for (INode child : childrenList) {
      child.computeQuotaUsage(counts, useCache, lastSnapshotId);
    }
   
    counts.add(Quota.NAMESPACE, 1);
    return counts;
  }
 
  @Override
  public Quota.Counts computeQuotaUsage4CurrentDirectory(Quota.Counts counts) {
    super.computeQuotaUsage4CurrentDirectory(counts);
    for(DirectoryDiff d : diffs) {
      for(INode deleted : d.getChildrenDiff().getList(ListType.DELETED)) {
        deleted.computeQuotaUsage(counts, false, Snapshot.INVALID_ID);
      }
    }
    counts.add(Quota.NAMESPACE, diffs.asList().size());
    return counts;
  }

  @Override
  public Content.Counts computeContentSummary(final Content.Counts counts) {
    super.computeContentSummary(counts);
    computeContentSummary4Snapshot(counts);
    return counts;
  }

  private void computeContentSummary4Snapshot(final Content.Counts counts) {
    for(DirectoryDiff d : diffs) {
      for(INode deleted : d.getChildrenDiff().getList(ListType.DELETED)) {
        deleted.computeContentSummary(counts);
      }
    }
    counts.add(Content.DIRECTORY, diffs.asList().size());
  }
 
  /**
   * Destroy a subtree under a DstReference node.
   */
  public static void destroyDstSubtree(INode inode, final Snapshot snapshot,
      final Snapshot prior, final BlocksMapUpdateInfo collectedBlocks,
      final List<INode> removedINodes) throws QuotaExceededException {
    Preconditions.checkArgument(prior != null);
    if (inode.isReference()) {
      if (inode instanceof INodeReference.WithName && snapshot != null) {
        // this inode has been renamed before the deletion of the DstReference
        // subtree
        inode.cleanSubtree(snapshot, prior, collectedBlocks, removedINodes,
            true);
      } else {
        // for DstReference node, continue this process to its subtree
        destroyDstSubtree(inode.asReference().getReferredINode(), snapshot,
            prior, collectedBlocks, removedINodes);
      }
    } else if (inode.isFile() && snapshot != null) {
      inode.cleanSubtree(snapshot, prior, collectedBlocks, removedINodes, true);
    } else if (inode.isDirectory()) {
      Map<INode, INode> excludedNodes = null;
      if (inode instanceof INodeDirectoryWithSnapshot) {
        INodeDirectoryWithSnapshot sdir = (INodeDirectoryWithSnapshot) inode;
        DirectoryDiffList diffList = sdir.getDiffs();
        if (snapshot != null) {
          diffList.deleteSnapshotDiff(snapshot, prior, sdir, collectedBlocks,
              removedINodes, true);
        }
        DirectoryDiff priorDiff = diffList.getDiff(prior);
        if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
          priorDiff.diff.destroyCreatedList(sdir, collectedBlocks,
              removedINodes);
          List<INode> dList = priorDiff.diff.getList(ListType.DELETED);
          excludedNodes = new HashMap<INode, INode>(dList.size());
          for (INode dNode : dList) {
            excludedNodes.put(dNode, dNode);
          }
        }
      }
      for (INode child : inode.asDirectory().getChildrenList(prior)) {
        if (excludedNodes != null && excludedNodes.containsKey(child)) {
          continue;
        }
        destroyDstSubtree(child, snapshot, prior, collectedBlocks,
            removedINodes);
      }
    }
  }
}
TOP

Related Classes of org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot$DirectoryDiffList

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.