1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 package org.apache.hadoop.hdfs.server.namenode; 19 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertTrue; 22 23 import java.io.File; 24 import java.io.IOException; 25 import java.util.ArrayList; 26 import java.util.EnumSet; 27 import java.util.List; 28 import java.util.Random; 29 30 import org.apache.commons.logging.impl.Log4JLogger; 31 import org.apache.hadoop.conf.Configuration; 32 import org.apache.hadoop.fs.FSDataOutputStream; 33 import org.apache.hadoop.fs.FileStatus; 34 import org.apache.hadoop.fs.Path; 35 import org.apache.hadoop.fs.permission.FsPermission; 36 import org.apache.hadoop.hdfs.DFSTestUtil; 37 import org.apache.hadoop.hdfs.DFSUtil; 38 import org.apache.hadoop.hdfs.DistributedFileSystem; 39 import org.apache.hadoop.hdfs.MiniDFSCluster; 40 import org.apache.hadoop.hdfs.client.HdfsDataOutputStream; 41 import org.apache.hadoop.hdfs.client.HdfsDataOutputStream.SyncFlag; 42 import org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction; 43 import org.apache.hadoop.hdfs.protocol.SnapshottableDirectoryStatus; 44 import org.apache.hadoop.hdfs.server.namenode.NNStorage.NameNodeFile; 45 import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiff; 46 import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; 47 import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotTestHelper; 48 import org.apache.hadoop.hdfs.util.Canceler; 49 import org.apache.log4j.Level; 50 import org.junit.After; 51 import org.junit.Assert; 52 import org.junit.Before; 53 import org.junit.Test; 54 55 /** 56 * Test FSImage save/load when Snapshot is supported 57 */ 58 public class TestFSImageWithSnapshot { 59 { SnapshotTestHelper.disableLogs()60 SnapshotTestHelper.disableLogs(); 61 ((Log4JLogger)INode.LOG).getLogger().setLevel(Level.ALL); 62 } 63 64 static final long seed = 0; 65 static final short REPLICATION = 3; 66 static final int BLOCKSIZE = 1024; 67 static final long txid = 1; 68 69 private final Path dir = new Path("/TestSnapshot"); 70 private static final String testDir = 71 System.getProperty("test.build.data", "build/test/data"); 72 73 Configuration conf; 74 MiniDFSCluster cluster; 75 FSNamesystem fsn; 76 DistributedFileSystem hdfs; 77 78 @Before setUp()79 public void setUp() throws Exception { 80 conf = new Configuration(); 81 cluster = new MiniDFSCluster.Builder(conf).numDataNodes(REPLICATION) 82 .build(); 83 cluster.waitActive(); 84 fsn = cluster.getNamesystem(); 85 hdfs = cluster.getFileSystem(); 86 } 87 88 @After tearDown()89 public void tearDown() throws Exception { 90 if (cluster != null) { 91 cluster.shutdown(); 92 } 93 } 94 95 /** 96 * Create a temp fsimage file for testing. 97 * @param dir The directory where the fsimage file resides 98 * @param imageTxId The transaction id of the fsimage 99 * @return The file of the image file 100 */ getImageFile(String dir, long imageTxId)101 private File getImageFile(String dir, long imageTxId) { 102 return new File(dir, String.format("%s_%019d", NameNodeFile.IMAGE, 103 imageTxId)); 104 } 105 106 /** 107 * Create a temp file for dumping the fsdir 108 * @param dir directory for the temp file 109 * @param suffix suffix of of the temp file 110 * @return the temp file 111 */ getDumpTreeFile(String dir, String suffix)112 private File getDumpTreeFile(String dir, String suffix) { 113 return new File(dir, String.format("dumpTree_%s", suffix)); 114 } 115 116 /** 117 * Dump the fsdir tree to a temp file 118 * @param fileSuffix suffix of the temp file for dumping 119 * @return the temp file 120 */ dumpTree2File(String fileSuffix)121 private File dumpTree2File(String fileSuffix) throws IOException { 122 File file = getDumpTreeFile(testDir, fileSuffix); 123 SnapshotTestHelper.dumpTree2File(fsn.getFSDirectory(), file); 124 return file; 125 } 126 127 /** Append a file without closing the output stream */ appendFileWithoutClosing(Path file, int length)128 private HdfsDataOutputStream appendFileWithoutClosing(Path file, int length) 129 throws IOException { 130 byte[] toAppend = new byte[length]; 131 Random random = new Random(); 132 random.nextBytes(toAppend); 133 HdfsDataOutputStream out = (HdfsDataOutputStream) hdfs.append(file); 134 out.write(toAppend); 135 return out; 136 } 137 138 /** Save the fsimage to a temp file */ saveFSImageToTempFile()139 private File saveFSImageToTempFile() throws IOException { 140 SaveNamespaceContext context = new SaveNamespaceContext(fsn, txid, 141 new Canceler()); 142 FSImageFormatProtobuf.Saver saver = new FSImageFormatProtobuf.Saver(context); 143 FSImageCompression compression = FSImageCompression.createCompression(conf); 144 File imageFile = getImageFile(testDir, txid); 145 fsn.readLock(); 146 try { 147 saver.save(imageFile, compression); 148 } finally { 149 fsn.readUnlock(); 150 } 151 return imageFile; 152 } 153 154 /** Load the fsimage from a temp file */ loadFSImageFromTempFile(File imageFile)155 private void loadFSImageFromTempFile(File imageFile) throws IOException { 156 FSImageFormat.LoaderDelegator loader = FSImageFormat.newLoader(conf, fsn); 157 fsn.writeLock(); 158 fsn.getFSDirectory().writeLock(); 159 try { 160 loader.load(imageFile, false); 161 FSImage.updateCountForQuota(fsn.getBlockManager().getStoragePolicySuite(), 162 INodeDirectory.valueOf(fsn.getFSDirectory().getINode("/"), "/")); 163 } finally { 164 fsn.getFSDirectory().writeUnlock(); 165 fsn.writeUnlock(); 166 } 167 } 168 169 /** 170 * Test when there is snapshot taken on root 171 */ 172 @Test testSnapshotOnRoot()173 public void testSnapshotOnRoot() throws Exception { 174 final Path root = new Path("/"); 175 hdfs.allowSnapshot(root); 176 hdfs.createSnapshot(root, "s1"); 177 178 cluster.shutdown(); 179 cluster = new MiniDFSCluster.Builder(conf).format(false) 180 .numDataNodes(REPLICATION).build(); 181 cluster.waitActive(); 182 fsn = cluster.getNamesystem(); 183 hdfs = cluster.getFileSystem(); 184 185 // save namespace and restart cluster 186 hdfs.setSafeMode(SafeModeAction.SAFEMODE_ENTER); 187 hdfs.saveNamespace(); 188 hdfs.setSafeMode(SafeModeAction.SAFEMODE_LEAVE); 189 cluster.shutdown(); 190 cluster = new MiniDFSCluster.Builder(conf).format(false) 191 .numDataNodes(REPLICATION).build(); 192 cluster.waitActive(); 193 fsn = cluster.getNamesystem(); 194 hdfs = cluster.getFileSystem(); 195 196 INodeDirectory rootNode = fsn.dir.getINode4Write(root.toString()) 197 .asDirectory(); 198 assertTrue("The children list of root should be empty", 199 rootNode.getChildrenList(Snapshot.CURRENT_STATE_ID).isEmpty()); 200 // one snapshot on root: s1 201 List<DirectoryDiff> diffList = rootNode.getDiffs().asList(); 202 assertEquals(1, diffList.size()); 203 Snapshot s1 = rootNode.getSnapshot(DFSUtil.string2Bytes("s1")); 204 assertEquals(s1.getId(), diffList.get(0).getSnapshotId()); 205 206 // check SnapshotManager's snapshottable directory list 207 assertEquals(1, fsn.getSnapshotManager().getNumSnapshottableDirs()); 208 SnapshottableDirectoryStatus[] sdirs = fsn.getSnapshotManager() 209 .getSnapshottableDirListing(null); 210 assertEquals(root, sdirs[0].getFullPath()); 211 212 // save namespace and restart cluster 213 hdfs.setSafeMode(SafeModeAction.SAFEMODE_ENTER); 214 hdfs.saveNamespace(); 215 hdfs.setSafeMode(SafeModeAction.SAFEMODE_LEAVE); 216 cluster.shutdown(); 217 cluster = new MiniDFSCluster.Builder(conf).format(false) 218 .numDataNodes(REPLICATION).build(); 219 cluster.waitActive(); 220 fsn = cluster.getNamesystem(); 221 hdfs = cluster.getFileSystem(); 222 } 223 224 /** 225 * Testing steps: 226 * <pre> 227 * 1. Creating/modifying directories/files while snapshots are being taken. 228 * 2. Dump the FSDirectory tree of the namesystem. 229 * 3. Save the namesystem to a temp file (FSImage saving). 230 * 4. Restart the cluster and format the namesystem. 231 * 5. Load the namesystem from the temp file (FSImage loading). 232 * 6. Dump the FSDirectory again and compare the two dumped string. 233 * </pre> 234 */ 235 @Test testSaveLoadImage()236 public void testSaveLoadImage() throws Exception { 237 int s = 0; 238 // make changes to the namesystem 239 hdfs.mkdirs(dir); 240 SnapshotTestHelper.createSnapshot(hdfs, dir, "s" + ++s); 241 Path sub1 = new Path(dir, "sub1"); 242 hdfs.mkdirs(sub1); 243 hdfs.setPermission(sub1, new FsPermission((short)0777)); 244 Path sub11 = new Path(sub1, "sub11"); 245 hdfs.mkdirs(sub11); 246 checkImage(s); 247 248 hdfs.createSnapshot(dir, "s" + ++s); 249 Path sub1file1 = new Path(sub1, "sub1file1"); 250 Path sub1file2 = new Path(sub1, "sub1file2"); 251 DFSTestUtil.createFile(hdfs, sub1file1, BLOCKSIZE, REPLICATION, seed); 252 DFSTestUtil.createFile(hdfs, sub1file2, BLOCKSIZE, REPLICATION, seed); 253 checkImage(s); 254 255 hdfs.createSnapshot(dir, "s" + ++s); 256 Path sub2 = new Path(dir, "sub2"); 257 Path sub2file1 = new Path(sub2, "sub2file1"); 258 Path sub2file2 = new Path(sub2, "sub2file2"); 259 DFSTestUtil.createFile(hdfs, sub2file1, BLOCKSIZE, REPLICATION, seed); 260 DFSTestUtil.createFile(hdfs, sub2file2, BLOCKSIZE, REPLICATION, seed); 261 checkImage(s); 262 263 hdfs.createSnapshot(dir, "s" + ++s); 264 hdfs.setReplication(sub1file1, (short) (REPLICATION - 1)); 265 hdfs.delete(sub1file2, true); 266 hdfs.setOwner(sub2, "dr.who", "unknown"); 267 hdfs.delete(sub2file1, true); 268 checkImage(s); 269 270 hdfs.createSnapshot(dir, "s" + ++s); 271 Path sub1_sub2file2 = new Path(sub1, "sub2file2"); 272 hdfs.rename(sub2file2, sub1_sub2file2); 273 274 hdfs.rename(sub1file1, sub2file1); 275 checkImage(s); 276 277 hdfs.rename(sub2file1, sub2file2); 278 checkImage(s); 279 } 280 checkImage(int s)281 void checkImage(int s) throws IOException { 282 final String name = "s" + s; 283 284 // dump the fsdir tree 285 File fsnBefore = dumpTree2File(name + "_before"); 286 287 // save the namesystem to a temp file 288 File imageFile = saveFSImageToTempFile(); 289 290 long numSdirBefore = fsn.getNumSnapshottableDirs(); 291 long numSnapshotBefore = fsn.getNumSnapshots(); 292 SnapshottableDirectoryStatus[] dirBefore = hdfs.getSnapshottableDirListing(); 293 294 // shutdown the cluster 295 cluster.shutdown(); 296 297 // dump the fsdir tree 298 File fsnBetween = dumpTree2File(name + "_between"); 299 SnapshotTestHelper.compareDumpedTreeInFile(fsnBefore, fsnBetween, true); 300 301 // restart the cluster, and format the cluster 302 cluster = new MiniDFSCluster.Builder(conf).format(true) 303 .numDataNodes(REPLICATION).build(); 304 cluster.waitActive(); 305 fsn = cluster.getNamesystem(); 306 hdfs = cluster.getFileSystem(); 307 308 // load the namesystem from the temp file 309 loadFSImageFromTempFile(imageFile); 310 311 // dump the fsdir tree again 312 File fsnAfter = dumpTree2File(name + "_after"); 313 314 // compare two dumped tree 315 SnapshotTestHelper.compareDumpedTreeInFile(fsnBefore, fsnAfter, true); 316 317 long numSdirAfter = fsn.getNumSnapshottableDirs(); 318 long numSnapshotAfter = fsn.getNumSnapshots(); 319 SnapshottableDirectoryStatus[] dirAfter = hdfs.getSnapshottableDirListing(); 320 321 Assert.assertEquals(numSdirBefore, numSdirAfter); 322 Assert.assertEquals(numSnapshotBefore, numSnapshotAfter); 323 Assert.assertEquals(dirBefore.length, dirAfter.length); 324 List<String> pathListBefore = new ArrayList<String>(); 325 for (SnapshottableDirectoryStatus sBefore : dirBefore) { 326 pathListBefore.add(sBefore.getFullPath().toString()); 327 } 328 for (SnapshottableDirectoryStatus sAfter : dirAfter) { 329 Assert.assertTrue(pathListBefore.contains(sAfter.getFullPath().toString())); 330 } 331 } 332 333 /** 334 * Test the fsimage saving/loading while file appending. 335 */ 336 @Test (timeout=60000) testSaveLoadImageWithAppending()337 public void testSaveLoadImageWithAppending() throws Exception { 338 Path sub1 = new Path(dir, "sub1"); 339 Path sub1file1 = new Path(sub1, "sub1file1"); 340 Path sub1file2 = new Path(sub1, "sub1file2"); 341 DFSTestUtil.createFile(hdfs, sub1file1, BLOCKSIZE, REPLICATION, seed); 342 DFSTestUtil.createFile(hdfs, sub1file2, BLOCKSIZE, REPLICATION, seed); 343 344 // 1. create snapshot s0 345 hdfs.allowSnapshot(dir); 346 hdfs.createSnapshot(dir, "s0"); 347 348 // 2. create snapshot s1 before appending sub1file1 finishes 349 HdfsDataOutputStream out = appendFileWithoutClosing(sub1file1, BLOCKSIZE); 350 out.hsync(EnumSet.of(SyncFlag.UPDATE_LENGTH)); 351 // also append sub1file2 352 DFSTestUtil.appendFile(hdfs, sub1file2, BLOCKSIZE); 353 hdfs.createSnapshot(dir, "s1"); 354 out.close(); 355 356 // 3. create snapshot s2 before appending finishes 357 out = appendFileWithoutClosing(sub1file1, BLOCKSIZE); 358 out.hsync(EnumSet.of(SyncFlag.UPDATE_LENGTH)); 359 hdfs.createSnapshot(dir, "s2"); 360 out.close(); 361 362 // 4. save fsimage before appending finishes 363 out = appendFileWithoutClosing(sub1file1, BLOCKSIZE); 364 out.hsync(EnumSet.of(SyncFlag.UPDATE_LENGTH)); 365 // dump fsdir 366 File fsnBefore = dumpTree2File("before"); 367 // save the namesystem to a temp file 368 File imageFile = saveFSImageToTempFile(); 369 370 // 5. load fsimage and compare 371 // first restart the cluster, and format the cluster 372 out.close(); 373 cluster.shutdown(); 374 cluster = new MiniDFSCluster.Builder(conf).format(true) 375 .numDataNodes(REPLICATION).build(); 376 cluster.waitActive(); 377 fsn = cluster.getNamesystem(); 378 hdfs = cluster.getFileSystem(); 379 // then load the fsimage 380 loadFSImageFromTempFile(imageFile); 381 382 // dump the fsdir tree again 383 File fsnAfter = dumpTree2File("after"); 384 385 // compare two dumped tree 386 SnapshotTestHelper.compareDumpedTreeInFile(fsnBefore, fsnAfter, true); 387 } 388 389 /** 390 * Test the fsimage loading while there is file under construction. 391 */ 392 @Test (timeout=60000) testLoadImageWithAppending()393 public void testLoadImageWithAppending() throws Exception { 394 Path sub1 = new Path(dir, "sub1"); 395 Path sub1file1 = new Path(sub1, "sub1file1"); 396 Path sub1file2 = new Path(sub1, "sub1file2"); 397 DFSTestUtil.createFile(hdfs, sub1file1, BLOCKSIZE, REPLICATION, seed); 398 DFSTestUtil.createFile(hdfs, sub1file2, BLOCKSIZE, REPLICATION, seed); 399 400 hdfs.allowSnapshot(dir); 401 hdfs.createSnapshot(dir, "s0"); 402 403 HdfsDataOutputStream out = appendFileWithoutClosing(sub1file1, BLOCKSIZE); 404 out.hsync(EnumSet.of(SyncFlag.UPDATE_LENGTH)); 405 406 // save namespace and restart cluster 407 hdfs.setSafeMode(SafeModeAction.SAFEMODE_ENTER); 408 hdfs.saveNamespace(); 409 hdfs.setSafeMode(SafeModeAction.SAFEMODE_LEAVE); 410 411 cluster.shutdown(); 412 cluster = new MiniDFSCluster.Builder(conf).format(false) 413 .numDataNodes(REPLICATION).build(); 414 cluster.waitActive(); 415 fsn = cluster.getNamesystem(); 416 hdfs = cluster.getFileSystem(); 417 } 418 419 /** 420 * Test fsimage loading when 1) there is an empty file loaded from fsimage, 421 * and 2) there is later an append operation to be applied from edit log. 422 */ 423 @Test (timeout=60000) testLoadImageWithEmptyFile()424 public void testLoadImageWithEmptyFile() throws Exception { 425 // create an empty file 426 Path file = new Path(dir, "file"); 427 FSDataOutputStream out = hdfs.create(file); 428 out.close(); 429 430 // save namespace 431 hdfs.setSafeMode(SafeModeAction.SAFEMODE_ENTER); 432 hdfs.saveNamespace(); 433 hdfs.setSafeMode(SafeModeAction.SAFEMODE_LEAVE); 434 435 // append to the empty file 436 out = hdfs.append(file); 437 out.write(1); 438 out.close(); 439 440 // restart cluster 441 cluster.shutdown(); 442 cluster = new MiniDFSCluster.Builder(conf).format(false) 443 .numDataNodes(REPLICATION).build(); 444 cluster.waitActive(); 445 hdfs = cluster.getFileSystem(); 446 447 FileStatus status = hdfs.getFileStatus(file); 448 assertEquals(1, status.getLen()); 449 } 450 451 /** 452 * Testing a special case with snapshots. When the following steps happen: 453 * <pre> 454 * 1. Take snapshot s1 on dir. 455 * 2. Create new dir and files under subsubDir, which is descendant of dir. 456 * 3. Take snapshot s2 on dir. 457 * 4. Delete subsubDir. 458 * 5. Delete snapshot s2. 459 * </pre> 460 * When we merge the diff from s2 to s1 (since we deleted s2), we need to make 461 * sure all the files/dirs created after s1 should be destroyed. Otherwise 462 * we may save these files/dirs to the fsimage, and cause FileNotFound 463 * Exception while loading fsimage. 464 */ 465 @Test (timeout=300000) testSaveLoadImageAfterSnapshotDeletion()466 public void testSaveLoadImageAfterSnapshotDeletion() 467 throws Exception { 468 // create initial dir and subdir 469 Path dir = new Path("/dir"); 470 Path subDir = new Path(dir, "subdir"); 471 Path subsubDir = new Path(subDir, "subsubdir"); 472 hdfs.mkdirs(subsubDir); 473 474 // take snapshots on subdir and dir 475 SnapshotTestHelper.createSnapshot(hdfs, dir, "s1"); 476 477 // create new dir under initial dir 478 Path newDir = new Path(subsubDir, "newdir"); 479 Path newFile = new Path(newDir, "newfile"); 480 hdfs.mkdirs(newDir); 481 DFSTestUtil.createFile(hdfs, newFile, BLOCKSIZE, REPLICATION, seed); 482 483 // create another snapshot 484 SnapshotTestHelper.createSnapshot(hdfs, dir, "s2"); 485 486 // delete subsubdir 487 hdfs.delete(subsubDir, true); 488 489 // delete snapshot s2 490 hdfs.deleteSnapshot(dir, "s2"); 491 492 // restart cluster 493 cluster.shutdown(); 494 cluster = new MiniDFSCluster.Builder(conf).numDataNodes(REPLICATION) 495 .format(false).build(); 496 cluster.waitActive(); 497 fsn = cluster.getNamesystem(); 498 hdfs = cluster.getFileSystem(); 499 500 // save namespace to fsimage 501 hdfs.setSafeMode(SafeModeAction.SAFEMODE_ENTER); 502 hdfs.saveNamespace(); 503 hdfs.setSafeMode(SafeModeAction.SAFEMODE_LEAVE); 504 505 cluster.shutdown(); 506 cluster = new MiniDFSCluster.Builder(conf).format(false) 507 .numDataNodes(REPLICATION).build(); 508 cluster.waitActive(); 509 fsn = cluster.getNamesystem(); 510 hdfs = cluster.getFileSystem(); 511 } 512 }