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