/**
* 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.cassandra.db;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.Config.DiskFailurePolicy;
import org.apache.cassandra.db.Directories.DataDirectory;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class DirectoriesTest
{
private static File tempDataDir;
private static final String KS = "ks";
private static final String[] CFS = new String[] { "cf1", "ks" };
private static final Set<CFMetaData> CFM = new HashSet<>(CFS.length);
private static Map<String, List<File>> files = new HashMap<String, List<File>>();
@BeforeClass
public static void beforeClass() throws IOException
{
for (String cf : CFS)
{
CFM.add(new CFMetaData(KS, cf, ColumnFamilyType.Standard, null));
}
tempDataDir = File.createTempFile("cassandra", "unittest");
tempDataDir.delete(); // hack to create a temp dir
tempDataDir.mkdir();
Directories.overrideDataDirectoriesForTest(tempDataDir.getPath());
// Create two fake data dir for tests, one using CF directories, one that do not.
createTestFiles();
}
@AfterClass
public static void afterClass()
{
Directories.resetDataDirectoriesAfterTest();
FileUtils.deleteRecursive(tempDataDir);
}
private static void createTestFiles() throws IOException
{
for (CFMetaData cfm : CFM)
{
List<File> fs = new ArrayList<>();
files.put(cfm.cfName, fs);
File dir = cfDir(cfm);
dir.mkdirs();
createFakeSSTable(dir, cfm.cfName, 1, false, fs);
createFakeSSTable(dir, cfm.cfName, 2, true, fs);
File backupDir = new File(dir, Directories.BACKUPS_SUBDIR);
backupDir.mkdir();
createFakeSSTable(backupDir, cfm.cfName, 1, false, fs);
File snapshotDir = new File(dir, Directories.SNAPSHOT_SUBDIR + File.separator + "42");
snapshotDir.mkdirs();
createFakeSSTable(snapshotDir, cfm.cfName, 1, false, fs);
}
}
private static void createFakeSSTable(File dir, String cf, int gen, boolean temp, List<File> addTo) throws IOException
{
Descriptor desc = new Descriptor(dir, KS, cf, gen, temp);
for (Component c : new Component[]{ Component.DATA, Component.PRIMARY_INDEX, Component.FILTER })
{
File f = new File(desc.filenameFor(c));
f.createNewFile();
addTo.add(f);
}
}
private static File cfDir(CFMetaData metadata)
{
String cfId = ByteBufferUtil.bytesToHex(ByteBufferUtil.bytes(metadata.cfId));
return new File(tempDataDir, metadata.ksName + File.separator + metadata.cfName + "-" + cfId);
}
@Test
public void testStandardDirs()
{
for (CFMetaData cfm : CFM)
{
Directories directories = new Directories(cfm);
assertEquals(cfDir(cfm), directories.getDirectoryForNewSSTables());
Descriptor desc = new Descriptor(cfDir(cfm), KS, cfm.cfName, 1, false);
File snapshotDir = new File(cfDir(cfm), File.separator + Directories.SNAPSHOT_SUBDIR + File.separator + "42");
assertEquals(snapshotDir, Directories.getSnapshotDirectory(desc, "42"));
File backupsDir = new File(cfDir(cfm), File.separator + Directories.BACKUPS_SUBDIR);
assertEquals(backupsDir, Directories.getBackupsDirectory(desc));
}
}
@Test
public void testSSTableLister()
{
for (CFMetaData cfm : CFM)
{
Directories directories = new Directories(cfm);
Directories.SSTableLister lister;
Set<File> listed;
// List all but no snapshot, backup
lister = directories.sstableLister();
listed = new HashSet<>(lister.listFiles());
for (File f : files.get(cfm.cfName))
{
if (f.getPath().contains(Directories.SNAPSHOT_SUBDIR) || f.getPath().contains(Directories.BACKUPS_SUBDIR))
assert !listed.contains(f) : f + " should not be listed";
else
assert listed.contains(f) : f + " is missing";
}
// List all but including backup (but no snapshot)
lister = directories.sstableLister().includeBackups(true);
listed = new HashSet<>(lister.listFiles());
for (File f : files.get(cfm.cfName))
{
if (f.getPath().contains(Directories.SNAPSHOT_SUBDIR))
assert !listed.contains(f) : f + " should not be listed";
else
assert listed.contains(f) : f + " is missing";
}
// Skip temporary and compacted
lister = directories.sstableLister().skipTemporary(true);
listed = new HashSet<>(lister.listFiles());
for (File f : files.get(cfm.cfName))
{
if (f.getPath().contains(Directories.SNAPSHOT_SUBDIR) || f.getPath().contains(Directories.BACKUPS_SUBDIR))
assert !listed.contains(f) : f + " should not be listed";
else if (f.getName().contains("-tmp-"))
assert !listed.contains(f) : f + " should not be listed";
else
assert listed.contains(f) : f + " is missing";
}
}
}
@Test
public void testDiskFailurePolicy_best_effort() throws IOException
{
DiskFailurePolicy origPolicy = DatabaseDescriptor.getDiskFailurePolicy();
try
{
DatabaseDescriptor.setDiskFailurePolicy(DiskFailurePolicy.best_effort);
for (DataDirectory dd : Directories.dataFileLocations)
{
dd.location.setExecutable(false);
dd.location.setWritable(false);
}
CFMetaData cfm = new CFMetaData(KS, "bad", ColumnFamilyType.Standard, null);
Directories dir = new Directories(cfm);
for (File file : dir.getCFDirectories())
{
assertTrue(BlacklistedDirectories.isUnwritable(file));
}
}
finally
{
for (DataDirectory dd : Directories.dataFileLocations)
{
dd.location.setExecutable(true);
dd.location.setWritable(true);
}
DatabaseDescriptor.setDiskFailurePolicy(origPolicy);
}
}
@Test
public void testMTSnapshots() throws Exception
{
for (final CFMetaData cfm : CFM)
{
final Directories directories = new Directories(cfm);
assertEquals(cfDir(cfm), directories.getDirectoryForNewSSTables());
final String n = Long.toString(System.nanoTime());
Callable<File> directoryGetter = new Callable<File>() {
public File call() throws Exception {
Descriptor desc = new Descriptor(cfDir(cfm), KS, cfm.cfName, 1, false);
return Directories.getSnapshotDirectory(desc, n);
}
};
List<Future<File>> invoked = Executors.newFixedThreadPool(2).invokeAll(Arrays.asList(directoryGetter, directoryGetter));
for(Future<File> fut:invoked) {
assertTrue(fut.get().exists());
}
}
}
}