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