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.util.ArrayList;
023 import java.util.Collections;
024 import java.util.HashMap;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.concurrent.atomic.AtomicInteger;
028
029 import org.apache.hadoop.hdfs.DFSUtil;
030 import org.apache.hadoop.hdfs.protocol.SnapshotException;
031 import org.apache.hadoop.hdfs.protocol.SnapshottableDirectoryStatus;
032 import org.apache.hadoop.hdfs.server.namenode.FSDirectory;
033 import org.apache.hadoop.hdfs.server.namenode.FSImageFormat;
034 import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
035 import org.apache.hadoop.hdfs.server.namenode.INode;
036 import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo;
037 import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
038 import org.apache.hadoop.hdfs.server.namenode.INodesInPath;
039 import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable.SnapshotDiffInfo;
040
041 /**
042 * Manage snapshottable directories and their snapshots.
043 *
044 * This class includes operations that create, access, modify snapshots and/or
045 * snapshot-related data. In general, the locking structure of snapshot
046 * operations is: <br>
047 *
048 * 1. Lock the {@link FSNamesystem} lock in {@link FSNamesystem} before calling
049 * into {@link SnapshotManager} methods.<br>
050 * 2. Lock the {@link FSDirectory} lock for the {@link SnapshotManager} methods
051 * if necessary.
052 */
053 public class SnapshotManager implements SnapshotStats {
054 private boolean allowNestedSnapshots = false;
055 private final FSDirectory fsdir;
056 private static final int SNAPSHOT_ID_BIT_WIDTH = 24;
057
058 private final AtomicInteger numSnapshots = new AtomicInteger();
059
060 private int snapshotCounter = 0;
061
062 /** All snapshottable directories in the namesystem. */
063 private final Map<Long, INodeDirectorySnapshottable> snapshottables
064 = new HashMap<Long, INodeDirectorySnapshottable>();
065
066 public SnapshotManager(final FSDirectory fsdir) {
067 this.fsdir = fsdir;
068 }
069
070 /** Used in tests only */
071 void setAllowNestedSnapshots(boolean allowNestedSnapshots) {
072 this.allowNestedSnapshots = allowNestedSnapshots;
073 }
074
075 private void checkNestedSnapshottable(INodeDirectory dir, String path)
076 throws SnapshotException {
077 if (allowNestedSnapshots) {
078 return;
079 }
080
081 for(INodeDirectorySnapshottable s : snapshottables.values()) {
082 if (s.isAncestorDirectory(dir)) {
083 throw new SnapshotException(
084 "Nested snapshottable directories not allowed: path=" + path
085 + ", the subdirectory " + s.getFullPathName()
086 + " is already a snapshottable directory.");
087 }
088 if (dir.isAncestorDirectory(s)) {
089 throw new SnapshotException(
090 "Nested snapshottable directories not allowed: path=" + path
091 + ", the ancestor " + s.getFullPathName()
092 + " is already a snapshottable directory.");
093 }
094 }
095 }
096
097 /**
098 * Set the given directory as a snapshottable directory.
099 * If the path is already a snapshottable directory, update the quota.
100 */
101 public void setSnapshottable(final String path, boolean checkNestedSnapshottable)
102 throws IOException {
103 final INodesInPath iip = fsdir.getINodesInPath4Write(path);
104 final INodeDirectory d = INodeDirectory.valueOf(iip.getLastINode(), path);
105 if (checkNestedSnapshottable) {
106 checkNestedSnapshottable(d, path);
107 }
108
109
110 final INodeDirectorySnapshottable s;
111 if (d.isSnapshottable()) {
112 //The directory is already a snapshottable directory.
113 s = (INodeDirectorySnapshottable)d;
114 s.setSnapshotQuota(INodeDirectorySnapshottable.SNAPSHOT_LIMIT);
115 } else {
116 s = d.replaceSelf4INodeDirectorySnapshottable(iip.getLatestSnapshotId(),
117 fsdir.getINodeMap());
118 }
119 addSnapshottable(s);
120 }
121
122 /** Add the given snapshottable directory to {@link #snapshottables}. */
123 public void addSnapshottable(INodeDirectorySnapshottable dir) {
124 snapshottables.put(dir.getId(), dir);
125 }
126
127 /** Remove the given snapshottable directory from {@link #snapshottables}. */
128 private void removeSnapshottable(INodeDirectorySnapshottable s) {
129 snapshottables.remove(s.getId());
130 }
131
132 /** Remove snapshottable directories from {@link #snapshottables} */
133 public void removeSnapshottable(List<INodeDirectorySnapshottable> toRemove) {
134 if (toRemove != null) {
135 for (INodeDirectorySnapshottable s : toRemove) {
136 removeSnapshottable(s);
137 }
138 }
139 }
140
141 /**
142 * Set the given snapshottable directory to non-snapshottable.
143 *
144 * @throws SnapshotException if there are snapshots in the directory.
145 */
146 public void resetSnapshottable(final String path) throws IOException {
147 final INodesInPath iip = fsdir.getINodesInPath4Write(path);
148 final INodeDirectory d = INodeDirectory.valueOf(iip.getLastINode(), path);
149 if (!d.isSnapshottable()) {
150 // the directory is already non-snapshottable
151 return;
152 }
153 final INodeDirectorySnapshottable s = (INodeDirectorySnapshottable) d;
154 if (s.getNumSnapshots() > 0) {
155 throw new SnapshotException("The directory " + path + " has snapshot(s). "
156 + "Please redo the operation after removing all the snapshots.");
157 }
158
159 if (s == fsdir.getRoot()) {
160 s.setSnapshotQuota(0);
161 } else {
162 s.replaceSelf(iip.getLatestSnapshotId(), fsdir.getINodeMap());
163 }
164 removeSnapshottable(s);
165 }
166
167 /**
168 * Find the source root directory where the snapshot will be taken
169 * for a given path.
170 *
171 * @param path The directory path where the snapshot will be taken.
172 * @return Snapshottable directory.
173 * @throws IOException
174 * Throw IOException when the given path does not lead to an
175 * existing snapshottable directory.
176 */
177 public INodeDirectorySnapshottable getSnapshottableRoot(final String path
178 ) throws IOException {
179 final INodesInPath i = fsdir.getINodesInPath4Write(path);
180 return INodeDirectorySnapshottable.valueOf(i.getLastINode(), path);
181 }
182
183 /**
184 * Create a snapshot of the given path.
185 * It is assumed that the caller will perform synchronization.
186 *
187 * @param path
188 * The directory path where the snapshot will be taken.
189 * @param snapshotName
190 * The name of the snapshot.
191 * @throws IOException
192 * Throw IOException when 1) the given path does not lead to an
193 * existing snapshottable directory, and/or 2) there exists a
194 * snapshot with the given name for the directory, and/or 3)
195 * snapshot number exceeds quota
196 */
197 public String createSnapshot(final String path, String snapshotName
198 ) throws IOException {
199 INodeDirectorySnapshottable srcRoot = getSnapshottableRoot(path);
200
201 if (snapshotCounter == getMaxSnapshotID()) {
202 // We have reached the maximum allowable snapshot ID and since we don't
203 // handle rollover we will fail all subsequent snapshot creation
204 // requests.
205 //
206 throw new SnapshotException(
207 "Failed to create the snapshot. The FileSystem has run out of " +
208 "snapshot IDs and ID rollover is not supported.");
209 }
210
211 srcRoot.addSnapshot(snapshotCounter, snapshotName);
212
213 //create success, update id
214 snapshotCounter++;
215 numSnapshots.getAndIncrement();
216 return Snapshot.getSnapshotPath(path, snapshotName);
217 }
218
219 /**
220 * Delete a snapshot for a snapshottable directory
221 * @param path Path to the directory where the snapshot was taken
222 * @param snapshotName Name of the snapshot to be deleted
223 * @param collectedBlocks Used to collect information to update blocksMap
224 * @throws IOException
225 */
226 public void deleteSnapshot(final String path, final String snapshotName,
227 BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes)
228 throws IOException {
229 // parse the path, and check if the path is a snapshot path
230 // the INodeDirectorySnapshottable#valueOf method will throw Exception
231 // if the path is not for a snapshottable directory
232 INodeDirectorySnapshottable srcRoot = getSnapshottableRoot(path);
233 srcRoot.removeSnapshot(snapshotName, collectedBlocks, removedINodes);
234 numSnapshots.getAndDecrement();
235 }
236
237 /**
238 * Rename the given snapshot
239 * @param path
240 * The directory path where the snapshot was taken
241 * @param oldSnapshotName
242 * Old name of the snapshot
243 * @param newSnapshotName
244 * New name of the snapshot
245 * @throws IOException
246 * Throw IOException when 1) the given path does not lead to an
247 * existing snapshottable directory, and/or 2) the snapshot with the
248 * old name does not exist for the directory, and/or 3) there exists
249 * a snapshot with the new name for the directory
250 */
251 public void renameSnapshot(final String path, final String oldSnapshotName,
252 final String newSnapshotName) throws IOException {
253 // Find the source root directory path where the snapshot was taken.
254 // All the check for path has been included in the valueOf method.
255 final INodeDirectorySnapshottable srcRoot
256 = INodeDirectorySnapshottable.valueOf(fsdir.getINode(path), path);
257 // Note that renameSnapshot and createSnapshot are synchronized externally
258 // through FSNamesystem's write lock
259 srcRoot.renameSnapshot(path, oldSnapshotName, newSnapshotName);
260 }
261
262 @Override
263 public int getNumSnapshottableDirs() {
264 return snapshottables.size();
265 }
266
267 @Override
268 public int getNumSnapshots() {
269 return numSnapshots.get();
270 }
271
272 void setNumSnapshots(int num) {
273 numSnapshots.set(num);
274 }
275
276 int getSnapshotCounter() {
277 return snapshotCounter;
278 }
279
280 void setSnapshotCounter(int counter) {
281 snapshotCounter = counter;
282 }
283
284 INodeDirectorySnapshottable[] getSnapshottableDirs() {
285 return snapshottables.values().toArray(
286 new INodeDirectorySnapshottable[snapshottables.size()]);
287 }
288
289 /**
290 * Read values of {@link #snapshotCounter}, {@link #numSnapshots}, and
291 * all snapshots from the DataInput
292 */
293 public Map<Integer, Snapshot> read(DataInput in, FSImageFormat.Loader loader
294 ) throws IOException {
295 snapshotCounter = in.readInt();
296 numSnapshots.set(in.readInt());
297
298 // read snapshots
299 final Map<Integer, Snapshot> snapshotMap = new HashMap<Integer, Snapshot>();
300 for(int i = 0; i < numSnapshots.get(); i++) {
301 final Snapshot s = Snapshot.read(in, loader);
302 snapshotMap.put(s.getId(), s);
303 }
304 return snapshotMap;
305 }
306
307 /**
308 * List all the snapshottable directories that are owned by the current user.
309 * @param userName Current user name.
310 * @return Snapshottable directories that are owned by the current user,
311 * represented as an array of {@link SnapshottableDirectoryStatus}. If
312 * {@code userName} is null, return all the snapshottable dirs.
313 */
314 public SnapshottableDirectoryStatus[] getSnapshottableDirListing(
315 String userName) {
316 if (snapshottables.isEmpty()) {
317 return null;
318 }
319
320 List<SnapshottableDirectoryStatus> statusList =
321 new ArrayList<SnapshottableDirectoryStatus>();
322 for (INodeDirectorySnapshottable dir : snapshottables.values()) {
323 if (userName == null || userName.equals(dir.getUserName())) {
324 SnapshottableDirectoryStatus status = new SnapshottableDirectoryStatus(
325 dir.getModificationTime(), dir.getAccessTime(),
326 dir.getFsPermission(), dir.getUserName(), dir.getGroupName(),
327 dir.getLocalNameBytes(), dir.getId(),
328 dir.getChildrenNum(Snapshot.CURRENT_STATE_ID),
329 dir.getNumSnapshots(),
330 dir.getSnapshotQuota(), dir.getParent() == null ?
331 DFSUtil.EMPTY_BYTES :
332 DFSUtil.string2Bytes(dir.getParent().getFullPathName()));
333 statusList.add(status);
334 }
335 }
336 Collections.sort(statusList, SnapshottableDirectoryStatus.COMPARATOR);
337 return statusList.toArray(
338 new SnapshottableDirectoryStatus[statusList.size()]);
339 }
340
341 /**
342 * Compute the difference between two snapshots of a directory, or between a
343 * snapshot of the directory and its current tree.
344 */
345 public SnapshotDiffInfo diff(final String path, final String from,
346 final String to) throws IOException {
347 if ((from == null || from.isEmpty())
348 && (to == null || to.isEmpty())) {
349 // both fromSnapshot and toSnapshot indicate the current tree
350 return null;
351 }
352
353 // Find the source root directory path where the snapshots were taken.
354 // All the check for path has been included in the valueOf method.
355 INodesInPath inodesInPath = fsdir.getINodesInPath4Write(path.toString());
356 final INodeDirectorySnapshottable snapshotRoot = INodeDirectorySnapshottable
357 .valueOf(inodesInPath.getLastINode(), path);
358
359 return snapshotRoot.computeDiff(from, to);
360 }
361
362 public void clearSnapshottableDirs() {
363 snapshottables.clear();
364 }
365
366 /**
367 * Returns the maximum allowable snapshot ID based on the bit width of the
368 * snapshot ID.
369 *
370 * @return maximum allowable snapshot ID.
371 */
372 public int getMaxSnapshotID() {
373 return ((1 << SNAPSHOT_ID_BIT_WIDTH) - 1);
374 }
375 }