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