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 * @return Snapshottable directory. 177 * @throws IOException 178 * Throw IOException when the given path does not lead to an 179 * existing snapshottable directory. 180 */ 181 public INodeDirectory getSnapshottableRoot(final INodesInPath iip) 182 throws IOException { 183 final String path = iip.getPath(); 184 final INodeDirectory dir = INodeDirectory.valueOf(iip.getLastINode(), path); 185 if (!dir.isSnapshottable()) { 186 throw new SnapshotException( 187 "Directory is not a snapshottable directory: " + path); 188 } 189 return dir; 190 } 191 192 /** 193 * Create a snapshot of the given path. 194 * It is assumed that the caller will perform synchronization. 195 * 196 * @param iip the INodes resolved from the snapshottable directory's path 197 * @param snapshotName 198 * The name of the snapshot. 199 * @throws IOException 200 * Throw IOException when 1) the given path does not lead to an 201 * existing snapshottable directory, and/or 2) there exists a 202 * snapshot with the given name for the directory, and/or 3) 203 * snapshot number exceeds quota 204 */ 205 public String createSnapshot(final INodesInPath iip, String snapshotRoot, 206 String snapshotName) throws IOException { 207 INodeDirectory srcRoot = getSnapshottableRoot(iip); 208 209 if (snapshotCounter == getMaxSnapshotID()) { 210 // We have reached the maximum allowable snapshot ID and since we don't 211 // handle rollover we will fail all subsequent snapshot creation 212 // requests. 213 // 214 throw new SnapshotException( 215 "Failed to create the snapshot. The FileSystem has run out of " + 216 "snapshot IDs and ID rollover is not supported."); 217 } 218 219 srcRoot.addSnapshot(snapshotCounter, snapshotName); 220 221 //create success, update id 222 snapshotCounter++; 223 numSnapshots.getAndIncrement(); 224 return Snapshot.getSnapshotPath(snapshotRoot, snapshotName); 225 } 226 227 /** 228 * Delete a snapshot for a snapshottable directory 229 * @param snapshotName Name of the snapshot to be deleted 230 * @param collectedBlocks Used to collect information to update blocksMap 231 * @throws IOException 232 */ 233 public void deleteSnapshot(final INodesInPath iip, final String snapshotName, 234 BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) 235 throws IOException { 236 INodeDirectory srcRoot = getSnapshottableRoot(iip); 237 srcRoot.removeSnapshot(fsdir.getBlockStoragePolicySuite(), snapshotName, 238 collectedBlocks, removedINodes); 239 numSnapshots.getAndDecrement(); 240 } 241 242 /** 243 * Rename the given snapshot 244 * @param oldSnapshotName 245 * Old name of the snapshot 246 * @param newSnapshotName 247 * New name of the snapshot 248 * @throws IOException 249 * Throw IOException when 1) the given path does not lead to an 250 * existing snapshottable directory, and/or 2) the snapshot with the 251 * old name does not exist for the directory, and/or 3) there exists 252 * a snapshot with the new name for the directory 253 */ 254 public void renameSnapshot(final INodesInPath iip, final String snapshotRoot, 255 final String oldSnapshotName, final String newSnapshotName) 256 throws IOException { 257 final INodeDirectory srcRoot = getSnapshottableRoot(iip); 258 srcRoot.renameSnapshot(snapshotRoot, oldSnapshotName, newSnapshotName); 259 } 260 261 public int getNumSnapshottableDirs() { 262 return snapshottables.size(); 263 } 264 265 public int getNumSnapshots() { 266 return numSnapshots.get(); 267 } 268 269 void setNumSnapshots(int num) { 270 numSnapshots.set(num); 271 } 272 273 int getSnapshotCounter() { 274 return snapshotCounter; 275 } 276 277 void setSnapshotCounter(int counter) { 278 snapshotCounter = counter; 279 } 280 281 INodeDirectory[] getSnapshottableDirs() { 282 return snapshottables.values().toArray( 283 new INodeDirectory[snapshottables.size()]); 284 } 285 286 /** 287 * Write {@link #snapshotCounter}, {@link #numSnapshots}, 288 * and all snapshots to the DataOutput. 289 */ 290 public void write(DataOutput out) throws IOException { 291 out.writeInt(snapshotCounter); 292 out.writeInt(numSnapshots.get()); 293 294 // write all snapshots. 295 for(INodeDirectory snapshottableDir : snapshottables.values()) { 296 for (Snapshot s : snapshottableDir.getDirectorySnapshottableFeature() 297 .getSnapshotList()) { 298 s.write(out); 299 } 300 } 301 } 302 303 /** 304 * Read values of {@link #snapshotCounter}, {@link #numSnapshots}, and 305 * all snapshots from the DataInput 306 */ 307 public Map<Integer, Snapshot> read(DataInput in, FSImageFormat.Loader loader 308 ) throws IOException { 309 snapshotCounter = in.readInt(); 310 numSnapshots.set(in.readInt()); 311 312 // read snapshots 313 final Map<Integer, Snapshot> snapshotMap = new HashMap<Integer, Snapshot>(); 314 for(int i = 0; i < numSnapshots.get(); i++) { 315 final Snapshot s = Snapshot.read(in, loader); 316 snapshotMap.put(s.getId(), s); 317 } 318 return snapshotMap; 319 } 320 321 /** 322 * List all the snapshottable directories that are owned by the current user. 323 * @param userName Current user name. 324 * @return Snapshottable directories that are owned by the current user, 325 * represented as an array of {@link SnapshottableDirectoryStatus}. If 326 * {@code userName} is null, return all the snapshottable dirs. 327 */ 328 public SnapshottableDirectoryStatus[] getSnapshottableDirListing( 329 String userName) { 330 if (snapshottables.isEmpty()) { 331 return null; 332 } 333 334 List<SnapshottableDirectoryStatus> statusList = 335 new ArrayList<SnapshottableDirectoryStatus>(); 336 for (INodeDirectory dir : snapshottables.values()) { 337 if (userName == null || userName.equals(dir.getUserName())) { 338 SnapshottableDirectoryStatus status = new SnapshottableDirectoryStatus( 339 dir.getModificationTime(), dir.getAccessTime(), 340 dir.getFsPermission(), dir.getUserName(), dir.getGroupName(), 341 dir.getLocalNameBytes(), dir.getId(), 342 dir.getChildrenNum(Snapshot.CURRENT_STATE_ID), 343 dir.getDirectorySnapshottableFeature().getNumSnapshots(), 344 dir.getDirectorySnapshottableFeature().getSnapshotQuota(), 345 dir.getParent() == null ? DFSUtil.EMPTY_BYTES : 346 DFSUtil.string2Bytes(dir.getParent().getFullPathName())); 347 statusList.add(status); 348 } 349 } 350 Collections.sort(statusList, SnapshottableDirectoryStatus.COMPARATOR); 351 return statusList.toArray( 352 new SnapshottableDirectoryStatus[statusList.size()]); 353 } 354 355 /** 356 * Compute the difference between two snapshots of a directory, or between a 357 * snapshot of the directory and its current tree. 358 */ 359 public SnapshotDiffReport diff(final INodesInPath iip, 360 final String snapshotRootPath, final String from, 361 final String to) throws IOException { 362 // Find the source root directory path where the snapshots were taken. 363 // All the check for path has been included in the valueOf method. 364 final INodeDirectory snapshotRoot = getSnapshottableRoot(iip); 365 366 if ((from == null || from.isEmpty()) 367 && (to == null || to.isEmpty())) { 368 // both fromSnapshot and toSnapshot indicate the current tree 369 return new SnapshotDiffReport(snapshotRootPath, from, to, 370 Collections.<DiffReportEntry> emptyList()); 371 } 372 final SnapshotDiffInfo diffs = snapshotRoot 373 .getDirectorySnapshottableFeature().computeDiff(snapshotRoot, from, to); 374 return diffs != null ? diffs.generateReport() : new SnapshotDiffReport( 375 snapshotRootPath, from, to, Collections.<DiffReportEntry> emptyList()); 376 } 377 378 public void clearSnapshottableDirs() { 379 snapshottables.clear(); 380 } 381 382 /** 383 * Returns the maximum allowable snapshot ID based on the bit width of the 384 * snapshot ID. 385 * 386 * @return maximum allowable snapshot ID. 387 */ 388 public int getMaxSnapshotID() { 389 return ((1 << SNAPSHOT_ID_BIT_WIDTH) - 1); 390 } 391 392 private ObjectName mxBeanName; 393 394 public void registerMXBean() { 395 mxBeanName = MBeans.register("NameNode", "SnapshotInfo", this); 396 } 397 398 public void shutdown() { 399 MBeans.unregister(mxBeanName); 400 mxBeanName = null; 401 } 402 403 @Override // SnapshotStatsMXBean 404 public SnapshottableDirectoryStatus.Bean[] 405 getSnapshottableDirectories() { 406 List<SnapshottableDirectoryStatus.Bean> beans = 407 new ArrayList<SnapshottableDirectoryStatus.Bean>(); 408 for (INodeDirectory d : getSnapshottableDirs()) { 409 beans.add(toBean(d)); 410 } 411 return beans.toArray(new SnapshottableDirectoryStatus.Bean[beans.size()]); 412 } 413 414 @Override // SnapshotStatsMXBean 415 public SnapshotInfo.Bean[] getSnapshots() { 416 List<SnapshotInfo.Bean> beans = new ArrayList<SnapshotInfo.Bean>(); 417 for (INodeDirectory d : getSnapshottableDirs()) { 418 for (Snapshot s : d.getDirectorySnapshottableFeature().getSnapshotList()) { 419 beans.add(toBean(s)); 420 } 421 } 422 return beans.toArray(new SnapshotInfo.Bean[beans.size()]); 423 } 424 425 public static SnapshottableDirectoryStatus.Bean toBean(INodeDirectory d) { 426 return new SnapshottableDirectoryStatus.Bean( 427 d.getFullPathName(), 428 d.getDirectorySnapshottableFeature().getNumSnapshots(), 429 d.getDirectorySnapshottableFeature().getSnapshotQuota(), 430 d.getModificationTime(), 431 Short.valueOf(Integer.toOctalString( 432 d.getFsPermissionShort())), 433 d.getUserName(), 434 d.getGroupName()); 435 } 436 437 public static SnapshotInfo.Bean toBean(Snapshot s) { 438 return new SnapshotInfo.Bean( 439 s.getRoot().getLocalName(), s.getRoot().getFullPathName(), 440 s.getRoot().getModificationTime()); 441 } 442}