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 */
018package org.apache.hadoop.hdfs.server.namenode.snapshot;
019
020import java.io.DataInput;
021import java.io.IOException;
022import java.text.SimpleDateFormat;
023import java.util.Arrays;
024import java.util.Comparator;
025import java.util.Date;
026
027import org.apache.hadoop.classification.InterfaceAudience;
028import org.apache.hadoop.fs.Path;
029import org.apache.hadoop.hdfs.DFSUtil;
030import org.apache.hadoop.hdfs.protocol.HdfsConstants;
031import org.apache.hadoop.hdfs.server.namenode.AclFeature;
032import org.apache.hadoop.hdfs.server.namenode.FSImageFormat;
033import org.apache.hadoop.hdfs.server.namenode.INode;
034import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
035import org.apache.hadoop.hdfs.util.ReadOnlyList;
036
037import com.google.common.collect.Iterables;
038import com.google.common.collect.Lists;
039
040/** Snapshot of a sub-tree in the namesystem. */
041@InterfaceAudience.Private
042public class Snapshot implements Comparable<byte[]> {
043  /**
044   * This id is used to indicate the current state (vs. snapshots)
045   */
046  public static final int CURRENT_STATE_ID = Integer.MAX_VALUE - 1;
047  public static final int NO_SNAPSHOT_ID = -1;
048  
049  /**
050   * The pattern for generating the default snapshot name.
051   * E.g. s20130412-151029.033
052   */
053  private static final String DEFAULT_SNAPSHOT_NAME_PATTERN = "'s'yyyyMMdd-HHmmss.SSS";
054  
055  public static String generateDefaultSnapshotName() {
056    return new SimpleDateFormat(DEFAULT_SNAPSHOT_NAME_PATTERN).format(new Date());
057  }
058
059  public static String getSnapshotPath(String snapshottableDir,
060      String snapshotRelativePath) {
061    final StringBuilder b = new StringBuilder(snapshottableDir);
062    if (b.charAt(b.length() - 1) != Path.SEPARATOR_CHAR) {
063      b.append(Path.SEPARATOR);
064    }
065    return b.append(HdfsConstants.DOT_SNAPSHOT_DIR)
066        .append(Path.SEPARATOR)
067        .append(snapshotRelativePath)
068        .toString();
069  }
070  
071  /**
072   * Get the name of the given snapshot.
073   * @param s The given snapshot.
074   * @return The name of the snapshot, or an empty string if {@code s} is null
075   */
076  static String getSnapshotName(Snapshot s) {
077    return s != null ? s.getRoot().getLocalName() : "";
078  }
079  
080  public static int getSnapshotId(Snapshot s) {
081    return s == null ? CURRENT_STATE_ID : s.getId();
082  }
083
084  /**
085   * Compare snapshot with IDs, where null indicates the current status thus
086   * is greater than any non-null snapshot.
087   */
088  public static final Comparator<Snapshot> ID_COMPARATOR
089      = new Comparator<Snapshot>() {
090    @Override
091    public int compare(Snapshot left, Snapshot right) {
092      return ID_INTEGER_COMPARATOR.compare(Snapshot.getSnapshotId(left),
093          Snapshot.getSnapshotId(right));
094    }
095  };
096
097  /**
098   * Compare snapshot with IDs, where null indicates the current status thus
099   * is greater than any non-null ID.
100   */
101  public static final Comparator<Integer> ID_INTEGER_COMPARATOR
102      = new Comparator<Integer>() {
103    @Override
104    public int compare(Integer left, Integer right) {
105      // Snapshot.CURRENT_STATE_ID means the current state, thus should be the 
106      // largest
107      return left - right;
108    }
109  };
110
111  /**
112   * Find the latest snapshot that 1) covers the given inode (which means the
113   * snapshot was either taken on the inode or taken on an ancestor of the
114   * inode), and 2) was taken before the given snapshot (if the given snapshot 
115   * is not null).
116   * 
117   * @param inode the given inode that the returned snapshot needs to cover
118   * @param anchor the returned snapshot should be taken before this given id.
119   * @return id of the latest snapshot that covers the given inode and was taken 
120   *         before the the given snapshot (if it is not null).
121   */
122  public static int findLatestSnapshot(INode inode, final int anchor) {
123    int latest = NO_SNAPSHOT_ID;
124    for(; inode != null; inode = inode.getParent()) {
125      if (inode.isDirectory()) {
126        final INodeDirectory dir = inode.asDirectory();
127        if (dir.isWithSnapshot()) {
128          latest = dir.getDiffs().updatePrior(anchor, latest);
129        }
130      }
131    }
132    return latest;
133  }
134  
135  static Snapshot read(DataInput in, FSImageFormat.Loader loader)
136      throws IOException {
137    final int snapshotId = in.readInt();
138    final INode root = loader.loadINodeWithLocalName(false, in, false);
139    return new Snapshot(snapshotId, root.asDirectory(), null);
140  }
141
142  /** The root directory of the snapshot. */
143  static public class Root extends INodeDirectory {
144    Root(INodeDirectory other) {
145      // Always preserve ACL.
146      super(other, false, Lists.newArrayList(
147        Iterables.filter(Arrays.asList(other.getFeatures()), AclFeature.class))
148        .toArray(new Feature[0]));
149    }
150
151    @Override
152    public ReadOnlyList<INode> getChildrenList(int snapshotId) {
153      return getParent().getChildrenList(snapshotId);
154    }
155
156    @Override
157    public INode getChild(byte[] name, int snapshotId) {
158      return getParent().getChild(name, snapshotId);
159    }
160    
161    @Override
162    public String getFullPathName() {
163      return getSnapshotPath(getParent().getFullPathName(), getLocalName());
164    }
165  }
166
167  /** Snapshot ID. */
168  private final int id;
169  /** The root directory of the snapshot. */
170  private final Root root;
171
172  Snapshot(int id, String name, INodeDirectorySnapshottable dir) {
173    this(id, dir, dir);
174    this.root.setLocalName(DFSUtil.string2Bytes(name));
175  }
176
177  Snapshot(int id, INodeDirectory dir, INodeDirectorySnapshottable parent) {
178    this.id = id;
179    this.root = new Root(dir);
180
181    this.root.setParent(parent);
182  }
183  
184  public int getId() {
185    return id;
186  }
187
188  /** @return the root directory of the snapshot. */
189  public Root getRoot() {
190    return root;
191  }
192
193  @Override
194  public int compareTo(byte[] bytes) {
195    return root.compareTo(bytes);
196  }
197  
198  @Override
199  public boolean equals(Object that) {
200    if (this == that) {
201      return true;
202    } else if (that == null || !(that instanceof Snapshot)) {
203      return false;
204    }
205    return this.id == ((Snapshot)that).id;
206  }
207  
208  @Override
209  public int hashCode() {
210    return id;
211  }
212  
213  @Override
214  public String toString() {
215    return getClass().getSimpleName() + "." + root.getLocalName() + "(id=" + id + ")";
216  }
217}