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 }