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.IOException;
022    import java.text.SimpleDateFormat;
023    import java.util.Arrays;
024    import java.util.Comparator;
025    import java.util.Date;
026    
027    import org.apache.hadoop.classification.InterfaceAudience;
028    import org.apache.hadoop.fs.Path;
029    import org.apache.hadoop.hdfs.DFSUtil;
030    import org.apache.hadoop.hdfs.protocol.HdfsConstants;
031    import org.apache.hadoop.hdfs.server.namenode.AclFeature;
032    import org.apache.hadoop.hdfs.server.namenode.FSImageFormat;
033    import org.apache.hadoop.hdfs.server.namenode.INode;
034    import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
035    import org.apache.hadoop.hdfs.util.ReadOnlyList;
036    
037    import com.google.common.collect.Iterables;
038    import com.google.common.collect.Lists;
039    
040    /** Snapshot of a sub-tree in the namesystem. */
041    @InterfaceAudience.Private
042    public 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    }