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