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 */ 018package org.apache.hadoop.hdfs.server.namenode.snapshot; 019 020import java.io.DataInput; 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.concurrent.atomic.AtomicInteger; 028 029import org.apache.hadoop.hdfs.DFSUtil; 030import org.apache.hadoop.hdfs.protocol.SnapshotException; 031import org.apache.hadoop.hdfs.protocol.SnapshottableDirectoryStatus; 032import org.apache.hadoop.hdfs.server.namenode.FSDirectory; 033import org.apache.hadoop.hdfs.server.namenode.FSImageFormat; 034import org.apache.hadoop.hdfs.server.namenode.FSNamesystem; 035import org.apache.hadoop.hdfs.server.namenode.INode; 036import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo; 037import org.apache.hadoop.hdfs.server.namenode.INodeDirectory; 038import org.apache.hadoop.hdfs.server.namenode.INodesInPath; 039import 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 */ 053public 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}