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     */
018    package org.apache.hadoop.hdfs.server.namenode.snapshot;
019    
020    import java.io.DataInput;
021    import java.io.DataOutput;
022    import java.io.IOException;
023    import java.text.SimpleDateFormat;
024    import java.util.Arrays;
025    import java.util.Comparator;
026    import java.util.Date;
027    
028    import org.apache.hadoop.classification.InterfaceAudience;
029    import org.apache.hadoop.fs.Path;
030    import org.apache.hadoop.hdfs.DFSUtil;
031    import org.apache.hadoop.hdfs.protocol.HdfsConstants;
032    import org.apache.hadoop.hdfs.server.namenode.AclFeature;
033    import org.apache.hadoop.hdfs.server.namenode.FSImageFormat;
034    import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization;
035    import org.apache.hadoop.hdfs.server.namenode.INode;
036    import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
037    import org.apache.hadoop.hdfs.server.namenode.XAttrFeature;
038    import org.apache.hadoop.hdfs.util.ReadOnlyList;
039    
040    import com.google.common.base.Predicate;
041    import com.google.common.collect.Iterables;
042    import com.google.common.collect.Lists;
043    
044    /** Snapshot of a sub-tree in the namesystem. */
045    @InterfaceAudience.Private
046    public 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, INodeDirectory dir) {
188        this(id, dir, dir);
189        this.root.setLocalName(DFSUtil.string2Bytes(name));
190      }
191    
192      Snapshot(int id, INodeDirectory dir, INodeDirectory parent) {
193        this.id = id;
194        this.root = new Root(dir);
195        this.root.setParent(parent);
196      }
197      
198      public int getId() {
199        return id;
200      }
201    
202      /** @return the root directory of the snapshot. */
203      public Root getRoot() {
204        return root;
205      }
206    
207      @Override
208      public int compareTo(byte[] bytes) {
209        return root.compareTo(bytes);
210      }
211      
212      @Override
213      public boolean equals(Object that) {
214        if (this == that) {
215          return true;
216        } else if (that == null || !(that instanceof Snapshot)) {
217          return false;
218        }
219        return this.id == ((Snapshot)that).id;
220      }
221      
222      @Override
223      public int hashCode() {
224        return id;
225      }
226      
227      @Override
228      public String toString() {
229        return getClass().getSimpleName() + "." + root.getLocalName() + "(id=" + id + ")";
230      }
231    
232      /** Serialize the fields to out */
233      void write(DataOutput out) throws IOException {
234        out.writeInt(id);
235        // write root
236        FSImageSerialization.writeINodeDirectory(root, out);
237      }
238    }