blob: d592dd91d28479e7be788e1a49b43cd4b5a7ba3c [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.cassandra.service.snapshot;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileOutputStreamPlus;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.utils.FBUtilities.now;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertTrue;
public class TableSnapshotTest
{
@Before
public void setup()
{
DatabaseDescriptor.daemonInitialization();
}
@ClassRule
public static TemporaryFolder tempFolder = new TemporaryFolder();
public static Set<File> createFolders(TemporaryFolder temp) throws IOException
{
File folder = new File(temp.newFolder());
Set<File> folders = new HashSet<>();
for (String folderName : Arrays.asList("foo", "bar", "buzz"))
{
File subfolder = new File(folder, folderName);
subfolder.tryCreateDirectories();
assertThat(subfolder.exists());
folders.add(subfolder);
}
;
return folders;
}
@Test
public void testSnapshotExists() throws IOException
{
Set<File> folders = createFolders(tempFolder);
TableSnapshot snapshot = new TableSnapshot(
"ks",
"tbl",
UUID.randomUUID(),
"some",
null,
null,
folders,
false
);
assertThat(snapshot.exists()).isTrue();
folders.forEach(FileUtils::deleteRecursive);
assertThat(snapshot.exists()).isFalse();
}
@Test
public void testSnapshotExpiring() throws IOException
{
Set<File> folders = createFolders(tempFolder);
TableSnapshot snapshot = new TableSnapshot(
"ks",
"tbl",
UUID.randomUUID(),
"some",
null,
null,
folders,
false
);
assertThat(snapshot.isExpiring()).isFalse();
assertThat(snapshot.isExpired(now())).isFalse();
snapshot = new TableSnapshot(
"ks",
"tbl",
UUID.randomUUID(),
"some",
now(),
null,
folders,
false
);
assertThat(snapshot.isExpiring()).isFalse();
assertThat(snapshot.isExpired(now())).isFalse();
snapshot = new TableSnapshot(
"ks",
"tbl",
UUID.randomUUID(),
"some",
now(),
now().plusSeconds(1000),
folders,
false
);
assertThat(snapshot.isExpiring()).isTrue();
assertThat(snapshot.isExpired(now())).isFalse();
snapshot = new TableSnapshot(
"ks",
"tbl",
UUID.randomUUID(),
"some",
now(),
now().minusSeconds(1000),
folders,
false);
assertThat(snapshot.isExpiring()).isTrue();
assertThat(snapshot.isExpired(now())).isTrue();
}
private Long writeBatchToFile(File file) throws IOException
{
FileOutputStreamPlus out = new FileOutputStreamPlus(file);
out.write(1);
out.write(2);
out.write(3);
out.close();
return 3L;
}
@Test
public void testComputeSizeOnDisk() throws IOException
{
Set<File> folders = createFolders(tempFolder);
TableSnapshot tableDetails = new TableSnapshot(
"ks",
"tbl",
UUID.randomUUID(),
"some",
null,
null,
folders,
false);
Long res = 0L;
for (File dir : folders)
{
writeBatchToFile(new File(dir, "tmp"));
res += FileUtils.folderSize(dir);
}
assertThat(tableDetails.computeSizeOnDiskBytes()).isGreaterThan(0L);
assertThat(tableDetails.computeSizeOnDiskBytes()).isEqualTo(res);
}
@Test
public void testComputeTrueSize() throws IOException
{
Set<File> folders = createFolders(tempFolder);
TableSnapshot tableDetails = new TableSnapshot(
"ks",
"tbl",
UUID.randomUUID(),
"some",
null,
null,
folders,
false
);
Long res = 0L;
for (File dir : folders)
{
File file = new File(dir, "tmp");
writeBatchToFile(file);
res += file.length();
}
assertThat(tableDetails.computeTrueSizeBytes()).isGreaterThan(0L);
assertThat(tableDetails.computeTrueSizeBytes()).isEqualTo(res);
}
@Test
public void testGetCreatedAt() throws IOException
{
Set<File> folders = createFolders(tempFolder);
// When createdAt is not null, getCreatedAt() should return it
Instant createdAt = Instant.EPOCH;
TableSnapshot withCreatedAt = new TableSnapshot(
"ks",
"tbl",
UUID.randomUUID(),
"some1",
createdAt,
null,
folders,
false
);
assertThat(withCreatedAt.getCreatedAt()).isEqualTo(createdAt);
// When createdAt is null, it should return the snapshot folder minimum update time
TableSnapshot withoutCreatedAt = new TableSnapshot(
"ks",
"tbl",
UUID.randomUUID(),
"some1",
null,
null,
folders,
false
);
assertThat(withoutCreatedAt.getCreatedAt()).isEqualTo(Instant.ofEpochMilli(folders.stream().mapToLong(f -> f.lastModified()).min().getAsLong()));
}
@Test
public void testShouldClearSnapshot() throws Exception
{
// TableSnapshot variables -> ephemeral / true / false, createdAt -> null / notnull
Instant now = Instant.now();
String keyspace = "ks";
String table = "tbl";
UUID id = UUID.randomUUID();
String tag = "someTag";
Instant snapshotCreation = now.minusSeconds(60);
Set<File> folders = createFolders(tempFolder);
List<TableSnapshot> snapshots = new ArrayList<>();
for (boolean ephemeral : new boolean[]{ true, false })
for (Instant createdAt : new Instant[]{ snapshotCreation, null })
snapshots.add(new TableSnapshot(keyspace,
table,
id,
tag,
createdAt, // variable
null,
folders,
ephemeral)); // variable
List<Pair<String, Long>> testingMethodInputs = new ArrayList<>();
for (String testingTag : new String[] {null, "", tag, "someothertag"})
// 0 to deactive byTimestamp logic, now.toEpochMilli as true, snapshot minus 60s as false
for (long olderThanTimestamp : new long[] {0, now.toEpochMilli(), snapshotCreation.minusSeconds(60).toEpochMilli()})
testingMethodInputs.add(Pair.create(testingTag, olderThanTimestamp));
for (Pair<String, Long> methodInput : testingMethodInputs)
{
String testingTag = methodInput.left();
Long olderThanTimestamp = methodInput.right;
for (TableSnapshot snapshot : snapshots)
{
// if shouldClear method returns true, it is only in case
// 1. snapshot to clear is not ephemeral
// 2. tag to clear is null, empty, or it is equal to snapshot tag
// 3. byTimestamp is true
if (TableSnapshot.shouldClearSnapshot(testingTag, olderThanTimestamp).test(snapshot))
{
// shouldClearTag = true
boolean shouldClearTag = (testingTag == null || testingTag.isEmpty()) || snapshot.getTag().equals(testingTag);
// notEphemeral
boolean notEphemeral = !snapshot.isEphemeral();
// byTimestamp
boolean byTimestamp = true;
if (olderThanTimestamp > 0L)
{
Instant createdAt = snapshot.getCreatedAt();
if (createdAt != null)
byTimestamp = createdAt.isBefore(Instant.ofEpochMilli(olderThanTimestamp));
}
assertTrue(notEphemeral);
assertTrue(shouldClearTag);
assertTrue(byTimestamp);
}
}
}
}
@Test
public void testGetLiveFileFromSnapshotFile()
{
testGetLiveFileFromSnapshotFile("~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/snapshots/1643481737850/me-1-big-Data.db",
"~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/me-1-big-Data.db");
}
@Test
public void testGetLiveFileFromSnapshotIndexFile()
{
testGetLiveFileFromSnapshotFile("~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/snapshots/1643481737850/.tbl_val_idx/me-1-big-Summary.db",
"~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/.tbl_val_idx/me-1-big-Summary.db");
}
public void testGetLiveFileFromSnapshotFile(String snapshotFile, String expectedLiveFile)
{
assertThat(TableSnapshot.getLiveFileFromSnapshotFile(Paths.get(snapshotFile)).toString()).isEqualTo(expectedLiveFile);
}
}