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.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.FSImageFormat;
032    import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization;
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    /** Snapshot of a sub-tree in the namesystem. */
038    @InterfaceAudience.Private
039    public 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    }