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