blob: 80af690c2acadd012b49a6e13bea054c584978df [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hdfs.server.namenode.snapshot;
import com.google.common.base.Supplier;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Options;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.DFSTestUtil;
import org.apache.hadoop.hdfs.DistributedFileSystem;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.hdfs.protocol.HdfsConstants;
import org.apache.hadoop.hdfs.protocol.SnapshotException;
import org.apache.hadoop.hdfs.protocol.SnapshottableDirectoryStatus;
import org.apache.hadoop.test.GenericTestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Testing random FileSystem operations with random Snapshot operations.
*/
public class TestRandomOpsWithSnapshots {
private static final Logger LOG =
LoggerFactory.getLogger(TestRandomOpsWithSnapshots.class);
private static final short REPL = 3;
private static final long BLOCKSIZE = 1024;
private static final int TOTAL_FILECOUNT = 250;
private static final int MAX_NUM_ITERATIONS = 10;
private static final int MAX_NUM_FILESYSTEM_OPERATIONS = 50;
private static final int MAX_NUM_SNAPSHOT_OPERATIONS = 50;
private static final int MAX_NUM_SUB_DIRECTORIES_LEVEL = 10;
private static final int MAX_NUM_FILE_LENGTH = 100;
private static final int MIN_NUM_OPERATIONS = 25;
private static final String TESTDIRSTRING = "/testDir";
private static final String WITNESSDIRSTRING = "/WITNESSDIR";
private static final Path TESTDIR = new Path(TESTDIRSTRING);
private static final Path WITNESSDIR = new Path(WITNESSDIRSTRING);
private static List<Path> snapshottableDirectories = new ArrayList<Path>();
private static Map<Path, ArrayList<String>> pathToSnapshotsMap =
new HashMap<Path, ArrayList<String>>();
private static final Configuration CONFIG = new Configuration();
private MiniDFSCluster cluster;
private DistributedFileSystem hdfs;
private static Random generator = null;
private int numberFileCreated = 0;
private int numberFileDeleted = 0;
private int numberFileRenamed = 0;
private int numberDirectoryCreated = 0;
private int numberDirectoryDeleted = 0;
private int numberDirectoryRenamed = 0;
private int numberSnapshotCreated = 0;
private int numberSnapshotDeleted = 0;
private int numberSnapshotRenamed = 0;
// Operation directories
private enum OperationDirectories {
TestDir,
WitnessDir;
}
// Operation type
private enum OperationType {
FileSystem,
Snapshot;
}
// FileSystem & Snapshot operation
private enum Operations {
FileSystem_CreateFile(2 /*operation weight*/, OperationType.FileSystem),
FileSystem_DeleteFile(2, OperationType.FileSystem),
FileSystem_RenameFile(2, OperationType.FileSystem),
FileSystem_CreateDir(1, OperationType.FileSystem),
FileSystem_DeleteDir(1, OperationType.FileSystem),
FileSystem_RenameDir(2, OperationType.FileSystem),
Snapshot_CreateSnapshot(5, OperationType.Snapshot),
Snapshot_DeleteSnapshot(3, OperationType.Snapshot),
Snapshot_RenameSnapshot(2, OperationType.Snapshot);
private int weight;
private OperationType operationType;
Operations(int weight, OperationType type) {
this.weight = weight;
this.operationType = type;
}
private int getWeight() {
return weight;
}
private static final Operations[] VALUES = values();
private static int sumWeights(OperationType type) {
int sum = 0;
for (Operations value: VALUES) {
if (value.operationType == type) {
sum += value.getWeight();
}
}
return sum;
}
private static final int TOTAL_WEIGHT_FILESYSTEM =
sumWeights(OperationType.FileSystem);
private static final int TOTAL_WEIGHT_SNAPSHOT =
sumWeights(OperationType.Snapshot);
public static Operations getRandomOperation(OperationType type) {
int randomNum = 0;
Operations randomOperation = null;
switch (type) {
case FileSystem:
randomNum = generator.nextInt(TOTAL_WEIGHT_FILESYSTEM);
break;
case Snapshot:
randomNum = generator.nextInt(TOTAL_WEIGHT_SNAPSHOT);
break;
default:
break;
}
int currentWeightSum = 0;
for (Operations currentValue: VALUES) {
if (currentValue.operationType == type) {
if (randomNum <= (currentWeightSum + currentValue.getWeight())) {
randomOperation = currentValue;
break;
}
currentWeightSum += currentValue.getWeight();
}
}
return randomOperation;
}
}
@Before
public void setUp() throws Exception {
CONFIG.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, BLOCKSIZE);
cluster = new MiniDFSCluster.Builder(CONFIG).numDataNodes(REPL).
format(true).build();
cluster.waitActive();
hdfs = cluster.getFileSystem();
hdfs.mkdirs(TESTDIR);
hdfs.mkdirs(WITNESSDIR);
}
@After
public void tearDown() throws Exception {
if (cluster != null) {
cluster.shutdown();
cluster = null;
}
}
/*
* Random file system operations with snapshot operations in between.
*/
@Test(timeout = 900000)
public void testRandomOperationsWithSnapshots()
throws IOException, InterruptedException, TimeoutException {
// Set
long seed = System.currentTimeMillis();
LOG.info("testRandomOperationsWithSnapshots, seed to be used: " + seed);
generator = new Random(seed);
int fileLen = generator.nextInt(MAX_NUM_FILE_LENGTH);
createFiles(TESTDIRSTRING, fileLen);
// Get list of snapshottable directories
SnapshottableDirectoryStatus[] snapshottableDirectoryStatus =
hdfs.getSnapshottableDirListing();
for (SnapshottableDirectoryStatus ssds : snapshottableDirectoryStatus) {
snapshottableDirectories.add(ssds.getFullPath());
}
if (snapshottableDirectories.size() == 0) {
hdfs.allowSnapshot(hdfs.getHomeDirectory());
snapshottableDirectories.add(hdfs.getHomeDirectory());
}
int numberOfIterations = generator.nextInt(MAX_NUM_ITERATIONS);
LOG.info("Number of iterations: " + numberOfIterations);
int numberFileSystemOperations = generator.nextInt(
MAX_NUM_FILESYSTEM_OPERATIONS-MIN_NUM_OPERATIONS+1)+MIN_NUM_OPERATIONS;
LOG.info("Number of FileSystem operations: "+ numberFileSystemOperations);
int numberSnapshotOperations = generator.nextInt(
MAX_NUM_SNAPSHOT_OPERATIONS-MIN_NUM_OPERATIONS)+MIN_NUM_OPERATIONS;
LOG.info("Number of Snapshot operations: " + numberSnapshotOperations);
// Act && Verify
randomOperationsWithSnapshots(numberOfIterations,
numberFileSystemOperations, numberSnapshotOperations);
}
/*
* Based on input we're performing:
* random number of file system operations
* random number of snapshot operations
* restart name node making sure fsimage can be loaded successfully.
*/
public void randomOperationsWithSnapshots(int numberOfIterations,
int numberFileSystemOperations,
int numberSnapshotOperations)
throws IOException, InterruptedException, TimeoutException {
// random number of iterations
for (int i = 0; i < numberOfIterations; i++) {
// random number of FileSystem operations
for (int j = 0; j < numberFileSystemOperations; j++) {
Operations fsOperation =
Operations.getRandomOperation(OperationType.FileSystem);
LOG.info("fsOperation: " + fsOperation);
switch (fsOperation) {
case FileSystem_CreateDir:
createTestDir();
break;
case FileSystem_DeleteDir:
deleteTestDir();
break;
case FileSystem_RenameDir:
renameTestDir();
break;
case FileSystem_CreateFile:
createTestFile();
break;
case FileSystem_DeleteFile:
deleteTestFile();
break;
case FileSystem_RenameFile:
renameTestFile();
break;
default:
assertNull("Invalid FileSystem operation", fsOperation);
break;
}
}
// random number of Snapshot operations
for (int k = 0; k < numberSnapshotOperations; k++) {
Operations snapshotOperation =
Operations.getRandomOperation(OperationType.Snapshot);
LOG.info("snapshotOperation: " + snapshotOperation);
switch (snapshotOperation) {
case Snapshot_CreateSnapshot:
createSnapshot();
break;
case Snapshot_DeleteSnapshot:
deleteSnapshot();
break;
case Snapshot_RenameSnapshot:
renameSnapshot();
break;
default:
assertNull("Invalid Snapshot operation", snapshotOperation);
break;
}
}
// Verification
checkClusterHealth();
}
}
/* Create a new test directory. */
private void createTestDir() throws IOException {
if (snapshottableDirectories.size() > 0) {
int index = generator.nextInt(snapshottableDirectories.size());
Path parentDir = snapshottableDirectories.get(index);
Path newDir = new Path(parentDir, "createTestDir_" +
UUID.randomUUID().toString());
for (OperationDirectories dir : OperationDirectories.values()) {
if (dir == OperationDirectories.WitnessDir) {
newDir = new Path(getNewPathString(newDir.toString(),
TESTDIRSTRING, WITNESSDIRSTRING));
}
hdfs.mkdirs(newDir);
assertTrue("Directory exists", hdfs.exists(newDir));
LOG.info("Directory created: " + newDir);
numberDirectoryCreated++;
}
}
}
/* Delete an existing test directory. */
private void deleteTestDir() throws IOException {
if (snapshottableDirectories.size() > 0) {
int index = generator.nextInt(snapshottableDirectories.size());
Path deleteDir = snapshottableDirectories.get(index);
if (!pathToSnapshotsMap.containsKey(deleteDir)) {
boolean isWitnessDir = false;
for (OperationDirectories dir : OperationDirectories.values()) {
if (dir == OperationDirectories.WitnessDir) {
isWitnessDir = true;
deleteDir = new Path(getNewPathString(deleteDir.toString(),
TESTDIRSTRING, WITNESSDIRSTRING));
}
hdfs.delete(deleteDir, true);
assertFalse("Directory does not exist", hdfs.exists(deleteDir));
if (!isWitnessDir) {
snapshottableDirectories.remove(deleteDir);
}
LOG.info("Directory removed: " + deleteDir);
numberDirectoryDeleted++;
}
}
}
}
/* Rename an existing test directory. */
private void renameTestDir() throws IOException {
if (snapshottableDirectories.size() > 0) {
int index = generator.nextInt(snapshottableDirectories.size());
Path oldDir = snapshottableDirectories.get(index);
if (!pathToSnapshotsMap.containsKey(oldDir)) {
Path newDir = oldDir.suffix("_renameDir+"+UUID.randomUUID().toString());
for (OperationDirectories dir : OperationDirectories.values()) {
if (dir == OperationDirectories.WitnessDir) {
oldDir = new Path(getNewPathString(oldDir.toString(),
TESTDIRSTRING, WITNESSDIRSTRING));
newDir = new Path(getNewPathString(newDir.toString(),
TESTDIRSTRING, WITNESSDIRSTRING));
}
hdfs.rename(oldDir, newDir, Options.Rename.OVERWRITE);
assertTrue("Target directory exists", hdfs.exists(newDir));
assertFalse("Source directory does not exist",
hdfs.exists(oldDir));
if (dir == OperationDirectories.TestDir) {
snapshottableDirectories.remove(oldDir);
snapshottableDirectories.add(newDir);
}
LOG.info("Renamed directory:" + oldDir + " to directory: " + newDir);
numberDirectoryRenamed++;
}
}
}
}
/* Create a new snapshot. */
private void createSnapshot() throws IOException {
if (snapshottableDirectories.size() > 0) {
int index = generator.nextInt(snapshottableDirectories.size());
Path randomDir = snapshottableDirectories.get(index);
String snapshotName = Integer.toString(generator.nextInt()) + ".ss";
hdfs.createSnapshot(randomDir, snapshotName);
LOG.info("createSnapshot, directory: " + randomDir +
", snapshot name: " + snapshotName);
numberSnapshotCreated++;
if (pathToSnapshotsMap.containsKey(randomDir)) {
pathToSnapshotsMap.get(randomDir).add(snapshotName);
} else {
pathToSnapshotsMap.put(randomDir,
new ArrayList<String>(Arrays.asList(snapshotName)));
}
}
}
/* Delete an existing snapshot. */
private void deleteSnapshot() throws IOException {
if (!pathToSnapshotsMap.isEmpty()) {
int index = generator.nextInt(pathToSnapshotsMap.size());
Object[] snapshotPaths = pathToSnapshotsMap.keySet().toArray();
Path snapshotPath = (Path)snapshotPaths[index];
ArrayList<String> snapshotNameList=pathToSnapshotsMap.get(snapshotPath);
String snapshotNameToBeDeleted = snapshotNameList.get(
generator.nextInt(snapshotNameList.size()));
hdfs.deleteSnapshot(snapshotPath, snapshotNameToBeDeleted);
LOG.info("deleteSnapshot, directory: " + snapshotPath +
", snapshot name: " + snapshotNameToBeDeleted);
numberSnapshotDeleted++;
// Adjust pathToSnapshotsMap after snapshot deletion
if (snapshotNameList.size() == 1) {
pathToSnapshotsMap.remove(snapshotPath);
} else {
pathToSnapshotsMap.get(snapshotPath).remove(snapshotNameToBeDeleted);
}
}
}
/* Rename an existing snapshot. */
private void renameSnapshot() throws IOException {
if (!pathToSnapshotsMap.isEmpty()) {
int index = generator.nextInt(pathToSnapshotsMap.size());
Object[] snapshotPaths = pathToSnapshotsMap.keySet().toArray();
Path snapshotPath = (Path)snapshotPaths[index];
ArrayList<String> snapshotNameList =
pathToSnapshotsMap.get(snapshotPath);
String snapshotOldName = snapshotNameList.get(
generator.nextInt(snapshotNameList.size()));
String snapshotOldNameNoExt = snapshotOldName.substring(0,
snapshotOldName.lastIndexOf('.')-1);
String snapshotNewName = snapshotOldNameNoExt + "_rename.ss";
hdfs.renameSnapshot(snapshotPath, snapshotOldName, snapshotNewName);
LOG.info("renameSnapshot, directory:" + snapshotPath + ", snapshot name:"
+ snapshotOldName + " to " + snapshotNewName);
numberSnapshotRenamed++;
// Adjust pathToSnapshotsMap after snapshot deletion
pathToSnapshotsMap.get(snapshotPath).remove(snapshotOldName);
pathToSnapshotsMap.get(snapshotPath).add(snapshotNewName);
}
}
/* Create a new test file. */
private void createTestFile() throws IOException {
if (snapshottableDirectories.size() > 0) {
int index = generator.nextInt(snapshottableDirectories.size());
Path randomDir = snapshottableDirectories.get(index);
if (!randomDir.isRoot()) {
randomDir = randomDir.getParent();
}
Path newFile = new Path(randomDir, "createTestFile.log");
for (OperationDirectories dir : OperationDirectories.values()) {
if (dir == OperationDirectories.WitnessDir) {
newFile = new Path(getNewPathString(newFile.toString(),
TESTDIRSTRING, WITNESSDIRSTRING));
}
hdfs.createNewFile(newFile);
assertTrue("File exists", hdfs.exists(newFile));
LOG.info("createTestFile, file created: " + newFile);
numberFileCreated++;
}
}
}
/* Delete an existing test file. */
private void deleteTestFile() throws IOException {
if (snapshottableDirectories.size() > 0) {
int index = generator.nextInt(snapshottableDirectories.size());
Path randomDir = snapshottableDirectories.get(index);
FileStatus[] fileStatusList = hdfs.listStatus(randomDir);
for (FileStatus fsEntry : fileStatusList) {
if (fsEntry.isFile()) {
Path deleteFile = fsEntry.getPath();
for (OperationDirectories dir : OperationDirectories.values()) {
if (dir == OperationDirectories.WitnessDir) {
deleteFile = new Path(getNewPathString(deleteFile.toString(),
TESTDIRSTRING, WITNESSDIRSTRING));
}
hdfs.delete(deleteFile, false);
assertFalse("File does not exists",
hdfs.exists(deleteFile));
LOG.info("deleteTestFile, file deleted: " + deleteFile);
numberFileDeleted++;
}
break;
}
}
}
}
/* Rename an existing test file. */
private void renameTestFile() throws IOException {
if (snapshottableDirectories.size() > 0) {
int index = generator.nextInt(snapshottableDirectories.size());
Path randomDir = snapshottableDirectories.get(index);
FileStatus[] fileStatusList = hdfs.listStatus(randomDir);
for (FileStatus fsEntry : fileStatusList) {
if (fsEntry.isFile()) {
Path oldFile = fsEntry.getPath();
Path newFile = oldFile.suffix("_renameFile");
for (OperationDirectories dir : OperationDirectories.values()) {
if (dir == OperationDirectories.WitnessDir) {
oldFile = new Path(getNewPathString(oldFile.toString(),
TESTDIRSTRING, WITNESSDIRSTRING));
newFile = new Path(getNewPathString(newFile.toString(),
TESTDIRSTRING, WITNESSDIRSTRING));
}
hdfs.rename(oldFile, newFile, Options.Rename.OVERWRITE);
assertTrue("Target file exists", hdfs.exists(newFile));
assertFalse("Source file does not exist", hdfs.exists(oldFile));
LOG.info("Renamed file: " + oldFile + " to file: " + newFile);
numberFileRenamed++;
}
break;
}
}
}
}
/* Check cluster health. */
private void checkClusterHealth() throws IOException, InterruptedException,
TimeoutException {
// 1. FileStatus comparison between test and witness directory
FileStatus[] testDirStatus = hdfs.listStatus(TESTDIR);
FileStatus[] witnessDirStatus = hdfs.listStatus(WITNESSDIR);
assertEquals(witnessDirStatus.length, testDirStatus.length);
LOG.info("checkClusterHealth, number of entries verified.");
Arrays.sort(testDirStatus);
Arrays.sort(witnessDirStatus);
for (int i = 0; i < testDirStatus.length; i++) {
assertEquals(witnessDirStatus[i].getPermission(),
testDirStatus[i].getPermission());
assertEquals(witnessDirStatus[i].getOwner(),
testDirStatus[i].getOwner());
assertEquals(witnessDirStatus[i].getGroup(),
testDirStatus[i].getGroup());
assertEquals(witnessDirStatus[i].getLen(), testDirStatus[i].getLen());
assertEquals(witnessDirStatus[i].getBlockSize(),
testDirStatus[i].getBlockSize());
assertEquals(witnessDirStatus[i].hasAcl(), testDirStatus[i].hasAcl());
assertEquals(witnessDirStatus[i].isEncrypted(),
testDirStatus[i].isEncrypted());
assertEquals(witnessDirStatus[i].isErasureCoded(),
testDirStatus[i].isErasureCoded());
assertEquals(witnessDirStatus[i].isDirectory(),
testDirStatus[i].isDirectory());
assertEquals(witnessDirStatus[i].isFile(), testDirStatus[i].isFile());
}
LOG.info("checkClusterHealth, metadata verified.");
// Randomly decide whether we want to do a check point
if (generator.nextBoolean()) {
LOG.info("checkClusterHealth, doing a checkpoint on NN.");
hdfs.setSafeMode(HdfsConstants.SafeModeAction.SAFEMODE_ENTER);
hdfs.saveNamespace();
hdfs.setSafeMode(HdfsConstants.SafeModeAction.SAFEMODE_LEAVE);
}
/** Restart name node making sure loading from image successfully */
LOG.info("checkClusterHealth, restarting NN.");
cluster.restartNameNodes();
GenericTestUtils.waitFor(new Supplier<Boolean>() {
@Override
public Boolean get() {
return !cluster.getNameNode().isInSafeMode();
}
}, 10, 100000);
assertTrue("NameNode is up", cluster.getNameNode().isActiveState());
assertTrue("DataNode is up and running", cluster.isDataNodeUp());
assertTrue("Cluster is up and running", cluster.isClusterUp());
LOG.info("checkClusterHealth, cluster is healthy.");
printOperationStats();
}
/* Helper: create a file with a length of filelen. */
private void createFile(final String fileName, final long filelen,
boolean enableSnapshot) throws IOException {
FileSystem fs = cluster.getFileSystem();
Path filePath = new Path(fileName);
DFSTestUtil.createFile(fs, filePath, filelen, (short) 1, 0);
// Randomly allow snapshot on parent directly
if (enableSnapshot && generator.nextBoolean()) {
try {
hdfs.allowSnapshot(filePath.getParent());
} catch (SnapshotException e) {
LOG.info("createFile, exception setting snapshotable directory: " +
e.getMessage());
}
}
}
/* Helper: create a large number of directories and files. */
private void createFiles(String rootDir, int fileLength) throws IOException {
if (!rootDir.endsWith("/")) {
rootDir += "/";
}
// Create files in a directory with random depth, ranging from 0-10.
for (int i = 0; i < TOTAL_FILECOUNT; i++) {
String filename = rootDir;
int dirs = generator.nextInt(MAX_NUM_SUB_DIRECTORIES_LEVEL);
for (int j=i; j >=(i-dirs); j--) {
filename += j + "/";
}
filename += "file" + i;
createFile(filename, fileLength, true);
assertTrue("Test file created", hdfs.exists(new Path(filename)));
LOG.info("createFiles, file: " + filename + "was created");
String witnessFile =
filename.replaceAll(TESTDIRSTRING, WITNESSDIRSTRING);
createFile(witnessFile, fileLength, false);
assertTrue("Witness file exists",
hdfs.exists(new Path(witnessFile)));
LOG.info("createFiles, file: " + witnessFile + "was created");
}
}
/* Helper: replace all target string with replacement string in original
* string. */
private String getNewPathString(String originalString, String targetString,
String replacementString) {
String str = originalString.replaceAll(targetString, replacementString);
LOG.info("Original string: " + originalString);
LOG.info("New string: " + str);
return str;
}
private void printOperationStats() {
LOG.info("Operation statistics for this iteration: ");
LOG.info("Number of files created: " + numberFileCreated);
LOG.info("Number of files deleted: " + numberFileDeleted);
LOG.info("Number of files renamed: " + numberFileRenamed);
LOG.info("Number of directories created: " + numberDirectoryCreated);
LOG.info("Number of directories deleted: " + numberDirectoryDeleted);
LOG.info("Number of directories renamed: " + numberDirectoryRenamed);
LOG.info("Number of snapshots created: " + numberSnapshotCreated);
LOG.info("Number of snapshots deleted: " + numberSnapshotDeleted);
LOG.info("Number of snapshots renamed: " + numberSnapshotRenamed);
numberFileCreated = 0;
numberFileDeleted = 0;
numberFileRenamed = 0;
numberDirectoryCreated = 0;
numberDirectoryDeleted = 0;
numberDirectoryRenamed = 0;
numberSnapshotCreated = 0;
numberSnapshotDeleted = 0;
numberSnapshotRenamed = 0;
}
}