blob: d03a2dd69c8ceba2e8ceb998c4d5ddece9db94ba [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.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.io.util.File;
import org.assertj.core.util.Lists;
import static org.apache.cassandra.service.snapshot.SnapshotLoader.SNAPSHOT_DIR_PATTERN;
import static org.assertj.core.api.Assertions.assertThat;
public class SnapshotLoaderTest
{
static String DATA_DIR_1 = "data1";
static String DATA_DIR_2 = "data2";
static String DATA_DIR_3 = "data3";
static String[] DATA_DIRS = new String[]{DATA_DIR_1, DATA_DIR_2, DATA_DIR_3};
static String KEYSPACE_1 = "ks1";
static String TABLE1_NAME = "table_1";
static UUID TABLE1_ID = UUID.randomUUID();
static String TABLE2_NAME = "table2";
static UUID TABLE2_ID = UUID.randomUUID();
static String KEYSPACE_2 = "ks2";
static String TABLE3_NAME = "table_3";
static UUID TABLE3_ID = UUID.randomUUID();
static String TAG1 = "tag1";
static String TAG2 = "tag2";
static String TAG3 = "tag3";
static String INVALID_NAME = "#test#";
static String INVALID_ID = "XPTO";
@ClassRule
public static TemporaryFolder tmpDir = new TemporaryFolder();
@Test
public void testMatcher()
{
String INDEX_SNAPSHOT = "/user/.ccm/test/node1/data0/ks/indexed_table-24b241e0c58f11eca526336fc2c671ab/snapshots/test";
assertThat(SNAPSHOT_DIR_PATTERN.matcher(INDEX_SNAPSHOT).find()).isTrue();
String TABLE_SNAPSHOT = "/Users/pmottagomes/.ccm/test/node1/data0/ks/my_table-1a025b40c58f11eca526336fc2c671ab/snapshots/test";
assertThat(SNAPSHOT_DIR_PATTERN.matcher(TABLE_SNAPSHOT).find()).isTrue();
String DROPPED_SNAPSHOT = "/Users/pmottagomes/.ccm/test/node1/data0/ks/my_table-e5c58330c58d11eca526336fc2c671ab/snapshots/dropped-1650997415751-my_table";
assertThat(SNAPSHOT_DIR_PATTERN.matcher(DROPPED_SNAPSHOT).find()).isTrue();
}
@Test
public void testNoSnapshots() throws IOException
{
// Create table directories on all data directories without snapshots
File baseDir = new File(tmpDir.newFolder());
for (String dataDir : DATA_DIRS)
{
createDir(baseDir, dataDir, KEYSPACE_1, tableDirName(TABLE1_NAME, TABLE1_ID));
createDir(baseDir, dataDir, KEYSPACE_1, tableDirName(TABLE2_NAME, TABLE2_ID));
createDir(baseDir, dataDir, KEYSPACE_2, tableDirName(TABLE3_NAME, TABLE3_ID));
}
// Check no snapshots are found
SnapshotLoader loader = new SnapshotLoader(Arrays.asList(Paths.get(baseDir.toString(), DATA_DIR_1),
Paths.get(baseDir.toString(), DATA_DIR_2),
Paths.get(baseDir.toString(), DATA_DIR_3)));
assertThat(loader.loadSnapshots()).isEmpty();
}
@Test
public void testSnapshotsWithoutManifests() throws IOException
{
Set<File> tag1Files = new HashSet<>();
Set<File> tag2Files = new HashSet<>();
Set<File> tag3Files = new HashSet<>();
// Create one snapshot per table - without manifests:
// - ks1.t1 : tag1
// - ks1.t2 : tag2
// - ks2.t3 : tag3
File baseDir = new File(tmpDir.newFolder());
for (String dataDir : DATA_DIRS)
{
tag1Files.add(createDir(baseDir, dataDir, KEYSPACE_1, tableDirName(TABLE1_NAME, TABLE1_ID), Directories.SNAPSHOT_SUBDIR, TAG1));
tag2Files.add(createDir(baseDir, dataDir, KEYSPACE_1, tableDirName(TABLE2_NAME, TABLE2_ID), Directories.SNAPSHOT_SUBDIR, TAG2));
tag3Files.add(createDir(baseDir, dataDir, KEYSPACE_2, tableDirName(TABLE3_NAME, TABLE3_ID), Directories.SNAPSHOT_SUBDIR, TAG3));
}
// Verify all 3 snapshots are found correctly from data directories
SnapshotLoader loader = new SnapshotLoader(Arrays.asList(Paths.get(baseDir.toString(), DATA_DIR_1),
Paths.get(baseDir.toString(), DATA_DIR_2),
Paths.get(baseDir.toString(), DATA_DIR_3)));
Set<TableSnapshot> snapshots = loader.loadSnapshots();
assertThat(snapshots).hasSize(3);
assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_1, TABLE1_NAME, TABLE1_ID, TAG1, null, null, tag1Files));
assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_1, TABLE2_NAME, TABLE2_ID, TAG2, null, null, tag2Files));
assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_2, TABLE3_NAME, TABLE3_ID, TAG3, null, null, tag3Files));
}
@Test
public void testSnapshotsWithManifests() throws IOException
{
Set<File> tag1Files = new HashSet<>();
Set<File> tag2Files = new HashSet<>();
Set<File> tag3Files = new HashSet<>();
// Create one snapshot per table:
// - ks1.t1 : tag1
// - ks1.t2 : tag2
// - ks2.t3 : tag3
File baseDir = new File(tmpDir.newFolder());
for (String dataDir : DATA_DIRS)
{
tag1Files.add(createDir(baseDir, dataDir, KEYSPACE_1, tableDirName(TABLE1_NAME, TABLE1_ID), Directories.SNAPSHOT_SUBDIR, TAG1));
tag2Files.add(createDir(baseDir, dataDir, KEYSPACE_1, tableDirName(TABLE2_NAME, TABLE2_ID), Directories.SNAPSHOT_SUBDIR, TAG2));
tag3Files.add(createDir(baseDir, dataDir, KEYSPACE_2, tableDirName(TABLE3_NAME, TABLE3_ID), Directories.SNAPSHOT_SUBDIR, TAG3));
}
// Write manifest for snapshot tag1 on random location
Instant tag1Ts = Instant.now();
File tag1ManifestLocation = tag1Files.toArray(new File[0])[ThreadLocalRandom.current().nextInt(tag1Files.size())];
writeManifest(tag1ManifestLocation, tag1Ts, null);
// Write manifest for snapshot tag2 on random location
Instant tag2Ts = Instant.now().plusSeconds(10);
DurationSpec.IntSecondsBound tag2Ttl = new DurationSpec.IntSecondsBound("10h");
File tag2ManifestLocation = tag2Files.toArray(new File[0])[ThreadLocalRandom.current().nextInt(tag2Files.size())];
writeManifest(tag2ManifestLocation, tag2Ts, tag2Ttl);
// Write manifest for snapshot tag3 on random location
Instant tag3Ts = Instant.now().plusSeconds(20);
File tag3ManifestLocation = tag3Files.toArray(new File[0])[ThreadLocalRandom.current().nextInt(tag3Files.size())];
writeManifest(tag3ManifestLocation, tag3Ts, null);
// Verify all 3 snapshots are found correctly from data directories
SnapshotLoader loader = new SnapshotLoader(Arrays.asList(Paths.get(baseDir.toString(), DATA_DIR_1),
Paths.get(baseDir.toString(), DATA_DIR_2),
Paths.get(baseDir.toString(), DATA_DIR_3)));
Set<TableSnapshot> snapshots = loader.loadSnapshots();
assertThat(snapshots).hasSize(3);
assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_1, TABLE1_NAME, TABLE1_ID, TAG1, tag1Ts, null, tag1Files));
assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_1, TABLE2_NAME, TABLE2_ID, TAG2, tag2Ts, tag2Ts.plusSeconds(tag2Ttl.toSeconds()), tag2Files));
assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_2, TABLE3_NAME, TABLE3_ID, TAG3, tag3Ts, null, tag3Files));
}
@Test
public void testInvalidSnapshotsAreNotLoaded() throws IOException
{
Set<File> tag1Files = new HashSet<>();
Set<File> tag2Files = new HashSet<>();
Set<File> tag3Files = new HashSet<>();
// Create invalid snapshot directory structure
// - /data_dir/#test#/table1-validuuid/snapshot/tag1
// - /data_dir/ks1/#test#-validuuid/snapshot/tag2
// - /data_dir/ks2/table3-invaliduuid/snapshot/tag3
File baseDir = new File(tmpDir.newFolder());
for (String dataDir : DATA_DIRS)
{
tag1Files.add(createDir(baseDir, dataDir, INVALID_NAME, tableDirName(TABLE1_NAME, TABLE1_ID), Directories.SNAPSHOT_SUBDIR, TAG1));
tag2Files.add(createDir(baseDir, dataDir, KEYSPACE_1, tableDirName(INVALID_NAME, TABLE2_ID), Directories.SNAPSHOT_SUBDIR, TAG2));
tag3Files.add(createDir(baseDir, dataDir, KEYSPACE_2, String.format("%s-%s", TABLE3_NAME, INVALID_ID), Directories.SNAPSHOT_SUBDIR, TAG3));
}
// Check no snapshots are loaded
SnapshotLoader loader = new SnapshotLoader(Arrays.asList(Paths.get(baseDir.toString(), DATA_DIR_1),
Paths.get(baseDir.toString(), DATA_DIR_2),
Paths.get(baseDir.toString(), DATA_DIR_3)));
assertThat(loader.loadSnapshots()).isEmpty();
}
@Test
public void testParseUUID()
{
assertThat(SnapshotLoader.Visitor.parseUUID("c7e513243f0711ec9bbc0242ac130002")).isEqualTo(UUID.fromString("c7e51324-3f07-11ec-9bbc-0242ac130002"));
}
private void writeManifest(File snapshotDir, Instant creationTime, DurationSpec.IntSecondsBound ttl) throws IOException
{
SnapshotManifest manifest = new SnapshotManifest(Lists.newArrayList("f1", "f2", "f3"), ttl, creationTime);
manifest.serializeToJsonFile(getManifestFile(snapshotDir));
}
private static File createDir(File baseDir, String... subdirs)
{
File file = new File(Paths.get(baseDir.toString(), subdirs).toString());
file.toJavaIOFile().mkdirs();
return file;
}
static String tableDirName(String tableName, UUID tableId)
{
return String.format("%s-%s", tableName, removeDashes(tableId));
}
static String removeDashes(UUID id)
{
return id.toString().replace("-", "");
}
public static File getManifestFile(File snapshotDir)
{
return new File(snapshotDir, "manifest.json");
}
}