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