diff --git a/CHANGES.txt b/CHANGES.txt index 69de78c9cb..b75eddd30e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.2 + * Remove ephemeral snapshot marker file and introduce a flag to SnapshotManifest (CASSANDRA-16911) * Add a virtual table that exposes currently running queries (CASSANDRA-15241) * Allow sstableloader to specify table without relying on path (CASSANDRA-16584) * Fix TestGossipingPropertyFileSnitch.test_prefer_local_reconnect_on_listen_address (CASSANDRA-17700) diff --git a/NEWS.txt b/NEWS.txt index 3e016b7602..766ef59762 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -70,9 +70,14 @@ New features - Whether ALTER TABLE commands are allowed to mutate columns - Whether SimpleStrategy is allowed on keyspace creation or alteration - Maximum replication factor + - It is possible to list ephemeral snapshots by nodetool listsnaphots command when flag "-e" is specified. Upgrading --------- + - Emphemeral marker files for snapshots done by repairs are not created anymore, + there is a dedicated flag in snapshot manifest instead. On upgrade of a node to version 4.2, on node's start, in case there + are such ephemeral snapshots on disk, they will be deleted (same behaviour as before) and any new ephemeral snapshots + will stop to create ephemeral marker files as flag in a snapshot manifest was introduced instead. Deprecation ----------- diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java index a40e5c7ad1..94ca18084d 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java @@ -22,7 +22,7 @@ import java.io.PrintStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; -import java.nio.file.Files; +import java.nio.file.Path; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -155,6 +155,7 @@ import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.paxos.Ballot; import org.apache.cassandra.service.paxos.PaxosRepairHistory; import org.apache.cassandra.service.paxos.TablePaxosRepairHistory; +import org.apache.cassandra.service.snapshot.SnapshotLoader; import org.apache.cassandra.service.snapshot.SnapshotManifest; import org.apache.cassandra.service.snapshot.TableSnapshot; import org.apache.cassandra.streaming.TableStreamManager; @@ -1698,9 +1699,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner /** * Rewrites all SSTables according to specified parameters * - * @param skipIfCurrentVersion - if {@link true}, will rewrite only SSTables that have version older than the current one ({@link BigFormat#latestVersion}) + * @param skipIfCurrentVersion - if {@link true}, will rewrite only SSTables that have version older than the current one ({@link org.apache.cassandra.io.sstable.format.big.BigFormat#latestVersion}) * @param skipIfNewerThanTimestamp - max timestamp (local creation time) for SSTable; SSTables created _after_ this timestamp will be excluded from compaction - * @param skipIfCompressionMatches - if {@link true}, will rewrite only SSTables whose compression parameters are different from {@link CFMetaData#compressionParams()} + * @param skipIfCompressionMatches - if {@link true}, will rewrite only SSTables whose compression parameters are different from {@link TableMetadata#params#getCompressionParameters()} ()} * @param jobs number of jobs for parallel execution */ public CompactionManager.AllSSTableOpStatus sstablesRewrite(final boolean skipIfCurrentVersion, @@ -2039,7 +2040,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner .collect(Collectors.toCollection(HashSet::new)); // Create and write snapshot manifest - SnapshotManifest manifest = new SnapshotManifest(mapToDataFilenames(sstables), ttl, creationTime); + SnapshotManifest manifest = new SnapshotManifest(mapToDataFilenames(sstables), ttl, creationTime, ephemeral); File manifestFile = getDirectories().getSnapshotManifestFile(tag); writeSnapshotManifest(manifest, manifestFile); snapshotDirs.add(manifestFile.parent().toAbsolute()); // manifest may create empty snapshot dir @@ -2052,16 +2053,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner snapshotDirs.add(schemaFile.parent().toAbsolute()); // schema may create empty snapshot dir } - // Maybe create ephemeral marker - if (ephemeral) - { - File ephemeralSnapshotMarker = getDirectories().getNewEphemeralSnapshotMarkerFile(tag); - createEphemeralSnapshotMarkerFile(tag, ephemeralSnapshotMarker); - snapshotDirs.add(ephemeralSnapshotMarker.parent().toAbsolute()); // marker may create empty snapshot dir - } - - TableSnapshot snapshot = new TableSnapshot(metadata.keyspace, metadata.name, metadata.id.asUUID(), tag, - manifest.createdAt, manifest.expiresAt, snapshotDirs); + TableSnapshot snapshot = new TableSnapshot(metadata.keyspace, metadata.name, metadata.id.asUUID(), + tag, manifest.createdAt, manifest.expiresAt, snapshotDirs, + manifest.ephemeral); StorageService.instance.addSnapshot(snapshot); return snapshot; @@ -2106,34 +2100,19 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner } } - private void createEphemeralSnapshotMarkerFile(final String snapshot, File ephemeralSnapshotMarker) - { - try - { - if (!ephemeralSnapshotMarker.parent().exists()) - ephemeralSnapshotMarker.parent().tryCreateDirectories(); - - Files.createFile(ephemeralSnapshotMarker.toPath()); - if (logger.isTraceEnabled()) - logger.trace("Created ephemeral snapshot marker file on {}.", ephemeralSnapshotMarker.absolutePath()); - } - catch (IOException e) - { - logger.warn(String.format("Could not create marker file %s for ephemeral snapshot %s. " + - "In case there is a failure in the operation that created " + - "this snapshot, you may need to clean it manually afterwards.", - ephemeralSnapshotMarker.absolutePath(), snapshot), e); - } - } - protected static void clearEphemeralSnapshots(Directories directories) { RateLimiter clearSnapshotRateLimiter = DatabaseDescriptor.getSnapshotRateLimiter(); - for (String ephemeralSnapshot : directories.listEphemeralSnapshots()) + List ephemeralSnapshots = new SnapshotLoader(directories).loadSnapshots() + .stream() + .filter(TableSnapshot::isEphemeral) + .collect(Collectors.toList()); + + for (TableSnapshot ephemeralSnapshot : ephemeralSnapshots) { - logger.trace("Clearing ephemeral snapshot {} leftover from previous session.", ephemeralSnapshot); - Directories.clearSnapshot(ephemeralSnapshot, directories.getCFDirectories(), clearSnapshotRateLimiter); + logger.trace("Clearing ephemeral snapshot {} leftover from previous session.", ephemeralSnapshot.getId()); + Directories.clearSnapshot(ephemeralSnapshot.getTag(), directories.getCFDirectories(), clearSnapshotRateLimiter); } } diff --git a/src/java/org/apache/cassandra/db/Directories.java b/src/java/org/apache/cassandra/db/Directories.java index 8672377b75..97f0fecb8b 100644 --- a/src/java/org/apache/cassandra/db/Directories.java +++ b/src/java/org/apache/cassandra/db/Directories.java @@ -572,17 +572,6 @@ public class Directories return new File(snapshotDir, "schema.cql"); } - public File getNewEphemeralSnapshotMarkerFile(String snapshotName) - { - File snapshotDir = new File(getWriteableLocationAsFile(1L), join(SNAPSHOT_SUBDIR, snapshotName)); - return getEphemeralSnapshotMarkerFile(snapshotDir); - } - - private static File getEphemeralSnapshotMarkerFile(File snapshotDirectory) - { - return new File(snapshotDirectory, "ephemeral.snapshot"); - } - public static File getBackupsDirectory(Descriptor desc) { return getBackupsDirectory(desc.directory); @@ -983,18 +972,25 @@ public class Directories return snapshots; } - protected TableSnapshot buildSnapshot(String tag, SnapshotManifest manifest, Set snapshotDirs) { + private TableSnapshot buildSnapshot(String tag, SnapshotManifest manifest, Set snapshotDirs) + { + boolean ephemeral = manifest != null ? manifest.isEphemeral() : isLegacyEphemeralSnapshot(snapshotDirs); Instant createdAt = manifest == null ? null : manifest.createdAt; Instant expiresAt = manifest == null ? null : manifest.expiresAt; return new TableSnapshot(metadata.keyspace, metadata.name, metadata.id.asUUID(), tag, createdAt, expiresAt, - snapshotDirs); + snapshotDirs, ephemeral); + } + + private static boolean isLegacyEphemeralSnapshot(Set snapshotDirs) + { + return snapshotDirs.stream().map(d -> new File(d, "ephemeral.snapshot")).anyMatch(File::exists); } @VisibleForTesting protected static SnapshotManifest maybeLoadManifest(String keyspace, String table, String tag, Set snapshotDirs) { List manifests = snapshotDirs.stream().map(d -> new File(d, "manifest.json")) - .filter(d -> d.exists()).collect(Collectors.toList()); + .filter(File::exists).collect(Collectors.toList()); if (manifests.isEmpty()) { @@ -1018,42 +1014,6 @@ public class Directories return null; } - public List listEphemeralSnapshots() - { - final List ephemeralSnapshots = new LinkedList<>(); - for (File snapshot : listAllSnapshots()) - { - if (getEphemeralSnapshotMarkerFile(snapshot).exists()) - ephemeralSnapshots.add(snapshot.name()); - } - return ephemeralSnapshots; - } - - private List listAllSnapshots() - { - final List snapshots = new LinkedList<>(); - for (final File dir : dataPaths) - { - File snapshotDir = isSecondaryIndexFolder(dir) - ? new File(dir.parentPath(), SNAPSHOT_SUBDIR) - : new File(dir, SNAPSHOT_SUBDIR); - if (snapshotDir.exists() && snapshotDir.isDirectory()) - { - final File[] snapshotDirs = snapshotDir.tryList(); - if (snapshotDirs != null) - { - for (final File snapshot : snapshotDirs) - { - if (snapshot.isDirectory()) - snapshots.add(snapshot); - } - } - } - } - - return snapshots; - } - @VisibleForTesting protected Map> listSnapshotDirsByTag() { diff --git a/src/java/org/apache/cassandra/db/SnapshotDetailsTabularData.java b/src/java/org/apache/cassandra/db/SnapshotDetailsTabularData.java index 4e6ab116c4..c091ebd5e0 100644 --- a/src/java/org/apache/cassandra/db/SnapshotDetailsTabularData.java +++ b/src/java/org/apache/cassandra/db/SnapshotDetailsTabularData.java @@ -32,7 +32,8 @@ public class SnapshotDetailsTabularData "True size", "Size on disk", "Creation time", - "Expiration time",}; + "Expiration time", + "Ephemeral"}; private static final String[] ITEM_DESCS = new String[]{"snapshot_name", "keyspace_name", @@ -40,7 +41,8 @@ public class SnapshotDetailsTabularData "TrueDiskSpaceUsed", "TotalDiskSpaceUsed", "created_at", - "expires_at",}; + "expires_at", + "ephemeral"}; private static final String TYPE_NAME = "SnapshotDetails"; @@ -56,7 +58,7 @@ public class SnapshotDetailsTabularData { try { - ITEM_TYPES = new OpenType[]{ SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING }; + ITEM_TYPES = new OpenType[]{ SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING }; COMPOSITE_TYPE = new CompositeType(TYPE_NAME, ROW_DESC, ITEM_NAMES, ITEM_DESCS, ITEM_TYPES); @@ -77,8 +79,9 @@ public class SnapshotDetailsTabularData final String liveSize = FileUtils.stringifyFileSize(details.computeTrueSizeBytes()); String createdAt = safeToString(details.getCreatedAt()); String expiresAt = safeToString(details.getExpiresAt()); + String ephemeral = Boolean.toString(details.isEphemeral()); result.put(new CompositeDataSupport(COMPOSITE_TYPE, ITEM_NAMES, - new Object[]{ details.getTag(), details.getKeyspaceName(), details.getTableName(), liveSize, totalSize, createdAt, expiresAt })); + new Object[]{ details.getTag(), details.getKeyspaceName(), details.getTableName(), liveSize, totalSize, createdAt, expiresAt, ephemeral })); } catch (OpenDataException e) { diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java index cca409385a..90b9496138 100644 --- a/src/java/org/apache/cassandra/service/CassandraDaemon.java +++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java @@ -291,24 +291,13 @@ public class CassandraDaemon SSTableHeaderFix.fixNonFrozenUDTIfUpgradeFrom30(); - // clean up debris in the rest of the keyspaces - for (String keyspaceName : Schema.instance.getKeyspaces()) + try { - // Skip system as we've already cleaned it - if (keyspaceName.equals(SchemaConstants.SYSTEM_KEYSPACE_NAME)) - continue; - - for (TableMetadata cfm : Schema.instance.getTablesAndViews(keyspaceName)) - { - try - { - ColumnFamilyStore.scrubDataDirectories(cfm); - } - catch (StartupException e) - { - exitOrFail(e.returnCode, e.getMessage(), e.getCause()); - } - } + scrubDataDirectories(); + } + catch (StartupException e) + { + exitOrFail(e.returnCode, e.getMessage(), e.getCause()); } Keyspace.setInitialized(); @@ -579,6 +568,22 @@ public class CassandraDaemon VirtualKeyspaceRegistry.instance.register(SystemViewsKeyspace.instance); } + public void scrubDataDirectories() throws StartupException + { + // clean up debris in the rest of the keyspaces + for (String keyspaceName : Schema.instance.getKeyspaces()) + { + // Skip system as we've already cleaned it + if (keyspaceName.equals(SchemaConstants.SYSTEM_KEYSPACE_NAME)) + continue; + + for (TableMetadata cfm : Schema.instance.getTablesAndViews(keyspaceName)) + { + ColumnFamilyStore.scrubDataDirectories(cfm); + } + } + } + public synchronized void initializeClientTransports() { // Native transport diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index bf8c65a253..957daf395a 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -4136,6 +4136,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE public Map getSnapshotDetails(Map options) { boolean skipExpiring = options != null && Boolean.parseBoolean(options.getOrDefault("no_ttl", "false")); + boolean includeEphemeral = options != null && Boolean.parseBoolean(options.getOrDefault("include_ephemeral", "false")); SnapshotLoader loader = new SnapshotLoader(); Map snapshotMap = new HashMap<>(); @@ -4144,6 +4145,8 @@ public class StorageService extends NotificationBroadcasterSupport implements IE { if (skipExpiring && snapshot.isExpiring()) continue; + if (!includeEphemeral && snapshot.isEphemeral()) + continue; TabularDataSupport data = (TabularDataSupport) snapshotMap.get(snapshot.getTag()); if (data == null) diff --git a/src/java/org/apache/cassandra/service/snapshot/SnapshotLoader.java b/src/java/org/apache/cassandra/service/snapshot/SnapshotLoader.java index 5f50937bf4..5c90007ddd 100644 --- a/src/java/org/apache/cassandra/service/snapshot/SnapshotLoader.java +++ b/src/java/org/apache/cassandra/service/snapshot/SnapshotLoader.java @@ -74,6 +74,11 @@ public class SnapshotLoader extends SimpleFileVisitor this.dataDirectories = dataDirs; } + public SnapshotLoader(Directories directories) + { + this(directories.getCFDirectories().stream().map(File::toPath).collect(Collectors.toList())); + } + public Set loadSnapshots() { for (Path dataDir : dataDirectories) diff --git a/src/java/org/apache/cassandra/service/snapshot/SnapshotManifest.java b/src/java/org/apache/cassandra/service/snapshot/SnapshotManifest.java index ba840efb77..4ac9bdc03d 100644 --- a/src/java/org/apache/cassandra/service/snapshot/SnapshotManifest.java +++ b/src/java/org/apache/cassandra/service/snapshot/SnapshotManifest.java @@ -46,19 +46,25 @@ public class SnapshotManifest @JsonProperty("expires_at") public final Instant expiresAt; + @JsonProperty("ephemeral") + public final boolean ephemeral; + /** needed for jackson serialization */ @SuppressWarnings("unused") - private SnapshotManifest() { + private SnapshotManifest() + { this.files = null; this.createdAt = null; this.expiresAt = null; + this.ephemeral = false; } - public SnapshotManifest(List files, DurationSpec.IntSecondsBound ttl, Instant creationTime) + public SnapshotManifest(List files, DurationSpec.IntSecondsBound ttl, Instant creationTime, boolean ephemeral) { this.files = files; this.createdAt = creationTime; this.expiresAt = ttl == null ? null : createdAt.plusSeconds(ttl.toSeconds()); + this.ephemeral = ephemeral; } public List getFiles() @@ -76,6 +82,11 @@ public class SnapshotManifest return expiresAt; } + public boolean isEphemeral() + { + return ephemeral; + } + public void serializeToJsonFile(File outputFile) throws IOException { FBUtilities.serializeToJsonFile(this, outputFile); @@ -92,12 +103,15 @@ public class SnapshotManifest if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SnapshotManifest manifest = (SnapshotManifest) o; - return Objects.equals(files, manifest.files) && Objects.equals(createdAt, manifest.createdAt) && Objects.equals(expiresAt, manifest.expiresAt); + return Objects.equals(files, manifest.files) + && Objects.equals(createdAt, manifest.createdAt) + && Objects.equals(expiresAt, manifest.expiresAt) + && Objects.equals(ephemeral, manifest.ephemeral); } @Override public int hashCode() { - return Objects.hash(files, createdAt, expiresAt); + return Objects.hash(files, createdAt, expiresAt, ephemeral); } } diff --git a/src/java/org/apache/cassandra/service/snapshot/TableSnapshot.java b/src/java/org/apache/cassandra/service/snapshot/TableSnapshot.java index 93fe2455c4..bda147fc8c 100644 --- a/src/java/org/apache/cassandra/service/snapshot/TableSnapshot.java +++ b/src/java/org/apache/cassandra/service/snapshot/TableSnapshot.java @@ -45,6 +45,7 @@ public class TableSnapshot private final String tableName; private final UUID tableId; private final String tag; + private final boolean ephemeral; private final Instant createdAt; private final Instant expiresAt; @@ -53,7 +54,7 @@ public class TableSnapshot public TableSnapshot(String keyspaceName, String tableName, UUID tableId, String tag, Instant createdAt, Instant expiresAt, - Set snapshotDirs) + Set snapshotDirs, boolean ephemeral) { this.keyspaceName = keyspaceName; this.tableName = tableName; @@ -62,6 +63,7 @@ public class TableSnapshot this.createdAt = createdAt; this.expiresAt = expiresAt; this.snapshotDirs = snapshotDirs; + this.ephemeral = ephemeral; } /** @@ -124,6 +126,11 @@ public class TableSnapshot return snapshotDirs.stream().anyMatch(File::exists); } + public boolean isEphemeral() + { + return ephemeral; + } + public boolean isExpiring() { return expiresAt != null; @@ -193,13 +200,13 @@ public class TableSnapshot return Objects.equals(keyspaceName, snapshot.keyspaceName) && Objects.equals(tableName, snapshot.tableName) && Objects.equals(tableId, snapshot.tableId) && Objects.equals(tag, snapshot.tag) && Objects.equals(createdAt, snapshot.createdAt) && Objects.equals(expiresAt, snapshot.expiresAt) && - Objects.equals(snapshotDirs, snapshot.snapshotDirs); + Objects.equals(snapshotDirs, snapshot.snapshotDirs) && Objects.equals(ephemeral, snapshot.ephemeral); } @Override public int hashCode() { - return Objects.hash(keyspaceName, tableName, tableId, tag, createdAt, expiresAt, snapshotDirs); + return Objects.hash(keyspaceName, tableName, tableId, tag, createdAt, expiresAt, snapshotDirs, ephemeral); } @Override @@ -213,6 +220,7 @@ public class TableSnapshot ", createdAt=" + createdAt + ", expiresAt=" + expiresAt + ", snapshotDirs=" + snapshotDirs + + ", ephemeral=" + ephemeral + '}'; } @@ -224,6 +232,7 @@ public class TableSnapshot private Instant createdAt = null; private Instant expiresAt = null; + private boolean ephemeral; private final Set snapshotDirs = new HashSet<>(); @@ -239,12 +248,17 @@ public class TableSnapshot { snapshotDirs.add(snapshotDir); File manifestFile = new File(snapshotDir, "manifest.json"); - if (manifestFile.exists() && createdAt == null && expiresAt == null) { - loadTimestampsFromManifest(manifestFile); - } + if (manifestFile.exists() && createdAt == null && expiresAt == null) + loadMetadataFromManifest(manifestFile); + + // check if an ephemeral marker file exists only in case it is not already ephemeral + // by reading it from manifest + // TODO remove this on Cassandra 4.3 release, see CASSANDRA-16911 + if (!ephemeral && new File(snapshotDir, "ephemeral.snapshot").exists()) + ephemeral = true; } - private void loadTimestampsFromManifest(File manifestFile) + private void loadMetadataFromManifest(File manifestFile) { try { @@ -252,6 +266,9 @@ public class TableSnapshot SnapshotManifest manifest = SnapshotManifest.deserializeFromJsonFile(manifestFile); createdAt = manifest.createdAt; expiresAt = manifest.expiresAt; + // a snapshot may be ephemeral when it has a marker file (old way) or flag in manifest (new way) + if (!ephemeral) + ephemeral = manifest.ephemeral; } catch (IOException e) { @@ -261,7 +278,7 @@ public class TableSnapshot TableSnapshot build() { - return new TableSnapshot(keyspaceName, tableName, tableId, tag, createdAt, expiresAt, snapshotDirs); + return new TableSnapshot(keyspaceName, tableName, tableId, tag, createdAt, expiresAt, snapshotDirs, ephemeral); } } diff --git a/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java b/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java index b70a7a964c..803fe5a4f0 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java +++ b/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java @@ -40,6 +40,11 @@ public class ListSnapshots extends NodeToolCmd description = "Skip snapshots with TTL") private boolean noTTL = false; + @Option(title = "ephemeral", + name = { "-e", "--ephemeral" }, + description = "Include ephememeral snapshots") + private boolean includeEphemeral = false; + @Override public void execute(NodeProbe probe) { @@ -50,6 +55,7 @@ public class ListSnapshots extends NodeToolCmd Map options = new HashMap<>(); options.put("no_ttl", Boolean.toString(noTTL)); + options.put("include_ephemeral", Boolean.toString(includeEphemeral)); final Map snapshotDetails = probe.getSnapshotDetails(options); if (snapshotDetails.isEmpty()) @@ -62,7 +68,11 @@ public class ListSnapshots extends NodeToolCmd TableBuilder table = new TableBuilder(); // display column names only once final List indexNames = snapshotDetails.entrySet().iterator().next().getValue().getTabularType().getIndexNames(); - table.add(indexNames.toArray(new String[indexNames.size()])); + + if (includeEphemeral) + table.add(indexNames.toArray(new String[indexNames.size()])); + else + table.add(indexNames.subList(0, indexNames.size() - 1).toArray(new String[indexNames.size() - 1])); for (final Map.Entry snapshotDetail : snapshotDetails.entrySet()) { @@ -70,12 +80,15 @@ public class ListSnapshots extends NodeToolCmd for (Object eachValue : values) { final List value = (List) eachValue; - table.add(value.toArray(new String[value.size()])); + if (includeEphemeral) + table.add(value.toArray(new String[value.size()])); + else + table.add(value.subList(0, value.size() - 1).toArray(new String[value.size() - 1])); } } table.printTo(out); - out.println("\nTotal TrueDiskSpaceUsed: " + FileUtils.stringifyFileSize(trueSnapshotsSize) + "\n"); + out.println("\nTotal TrueDiskSpaceUsed: " + FileUtils.stringifyFileSize(trueSnapshotsSize) + '\n'); } catch (Exception e) { diff --git a/test/distributed/org/apache/cassandra/distributed/impl/Instance.java b/test/distributed/org/apache/cassandra/distributed/impl/Instance.java index 65e6579480..705a7f3289 100644 --- a/test/distributed/org/apache/cassandra/distributed/impl/Instance.java +++ b/test/distributed/org/apache/cassandra/distributed/impl/Instance.java @@ -114,6 +114,7 @@ import org.apache.cassandra.net.Verb; import org.apache.cassandra.schema.MigrationCoordinator; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaConstants; +import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.service.CassandraDaemon; import org.apache.cassandra.service.ClientState; @@ -619,6 +620,9 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance // Start up virtual table support CassandraDaemon.getInstanceForTesting().setupVirtualKeyspaces(); + // clean up debris in data directories + CassandraDaemon.getInstanceForTesting().scrubDataDirectories(); + Keyspace.setInitialized(); // Replay any CommitLogSegments found on disk diff --git a/test/distributed/org/apache/cassandra/distributed/test/EphemeralSnapshotTest.java b/test/distributed/org/apache/cassandra/distributed/test/EphemeralSnapshotTest.java new file mode 100644 index 0000000000..a9e804d071 --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/EphemeralSnapshotTest.java @@ -0,0 +1,164 @@ +/* + * 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.distributed.test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import com.google.common.util.concurrent.Futures; +import org.junit.Test; + +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.distributed.api.IInvokableInstance; +import org.apache.cassandra.distributed.api.IIsolatedExecutor; +import org.apache.cassandra.io.util.File; +import org.apache.cassandra.service.snapshot.SnapshotManifest; +import org.apache.cassandra.utils.Pair; + +import static java.lang.String.format; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.cassandra.distributed.api.ConsistencyLevel.ONE; +import static org.apache.cassandra.distributed.api.Feature.GOSSIP; +import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL; +import static org.apache.cassandra.distributed.api.Feature.NETWORK; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class EphemeralSnapshotTest extends TestBaseImpl +{ + private static final String snapshotName = "snapshotname"; + private static final String tableName = "city"; + + @Test + public void testStartupRemovesEphemeralSnapshotOnEphemeralFlagInManifest() throws Exception + { + try (Cluster c = init(builder().withNodes(1) + .withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL)) + .start())) + { + Pair initialisationData = initialise(c); + + String tableId = initialisationData.left; + String[] dataDirs = initialisationData.right; + + // rewrite manifest, pretend that it is ephemeral + Path manifestPath = findManifest(dataDirs, tableId); + SnapshotManifest manifest = SnapshotManifest.deserializeFromJsonFile(new File(manifestPath)); + SnapshotManifest manifestWithEphemeralFlag = new SnapshotManifest(manifest.files, null, manifest.createdAt, true); + manifestWithEphemeralFlag.serializeToJsonFile(new File(manifestPath)); + + verify(c.get(1)); + } + } + + // TODO this test might be deleted once we get rid of ephemeral marker file for good in 4.3 + @Test + public void testStartupRemovesEphemeralSnapshotOnMarkerFile() throws Exception + { + try (Cluster c = init(builder().withNodes(1) + .withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL)) + .start())) + { + Pair initialisationData = initialise(c); + + String tableId = initialisationData.left; + String[] dataDirs = initialisationData.right; + + // place ephemeral marker file into snapshot directory pretending it was created as ephemeral + Path ephemeralMarkerFile = Paths.get(dataDirs[0]) + .resolve(KEYSPACE) + .resolve(format("%s-%s", tableName, tableId)) + .resolve("snapshots") + .resolve(snapshotName) + .resolve("ephemeral.snapshot"); + + Files.createFile(ephemeralMarkerFile); + + verify(c.get(1)); + } + } + + private Pair initialise(Cluster c) + { + c.schemaChange(withKeyspace("CREATE TABLE IF NOT EXISTS %s." + tableName + " (cityid int PRIMARY KEY, name text)")); + c.coordinator(1).execute(withKeyspace("INSERT INTO %s." + tableName + "(cityid, name) VALUES (1, 'Canberra');"), ONE); + IInvokableInstance instance = c.get(1); + + instance.flush(KEYSPACE); + + assertEquals(0, instance.nodetool("snapshot", "-kt", withKeyspace("%s." + tableName), "-t", snapshotName)); + waitForSnapshot(instance, snapshotName); + + String tableId = instance.callOnInstance((IIsolatedExecutor.SerializableCallable) () -> { + return Keyspace.open(KEYSPACE).getMetadata().tables.get(tableName).get().id.asUUID().toString().replaceAll("-", ""); + }); + + String[] dataDirs = (String[]) instance.config().get("data_file_directories"); + + return Pair.create(tableId, dataDirs); + } + + + private void verify(IInvokableInstance instance) + { + // by default, we do not see ephemerals + assertFalse(instance.nodetoolResult("listsnapshots").getStdout().contains("snapshotname")); + + // we see them via -e flag + assertTrue(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains("snapshotname")); + + Futures.getUnchecked(instance.shutdown()); + + // startup should remove ephemeral marker file + instance.startup(); + + assertFalse(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains("snapshotname")); + } + + private void waitForSnapshot(IInvokableInstance instance, String snapshotName) + { + await().timeout(20, SECONDS) + .pollInterval(1, SECONDS) + .until(() -> instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName)); + } + + private Path findManifest(String[] dataDirs, String tableId) + { + for (String dataDir : dataDirs) + { + Path manifest = Paths.get(dataDir) + .resolve(KEYSPACE) + .resolve(format("%s-%s", tableName, tableId)) + .resolve("snapshots") + .resolve(snapshotName) + .resolve("manifest.json"); + + if (Files.exists(manifest)) + { + return manifest; + } + } + + throw new IllegalStateException("Unable to find manifest!"); + } +} diff --git a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java index 6d8bd0fe05..6268d3b5ed 100644 --- a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java +++ b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java @@ -170,7 +170,7 @@ public class ColumnFamilyStoreTest } @Test - public void testDeleteStandardRowSticksAfterFlush() throws Throwable + public void testDeleteStandardRowSticksAfterFlush() { // test to make sure flushing after a delete doesn't resurrect delted cols. String keyspaceName = KEYSPACE1; @@ -228,7 +228,7 @@ public class ColumnFamilyStoreTest } @Test - public void testClearEphemeralSnapshots() throws Throwable + public void testClearEphemeralSnapshots() { ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_INDEX1); diff --git a/test/unit/org/apache/cassandra/db/DirectoriesTest.java b/test/unit/org/apache/cassandra/db/DirectoriesTest.java index 252c630614..8e47157af3 100644 --- a/test/unit/org/apache/cassandra/db/DirectoriesTest.java +++ b/test/unit/org/apache/cassandra/db/DirectoriesTest.java @@ -20,6 +20,7 @@ package org.apache.cassandra.db; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -97,6 +98,7 @@ public class DirectoriesTest public static final String TABLE_NAME = "FakeTable"; public static final String SNAPSHOT1 = "snapshot1"; public static final String SNAPSHOT2 = "snapshot2"; + public static final String SNAPSHOT3 = "snapshot3"; public static final String LEGACY_SNAPSHOT_NAME = "42"; private static File tempDataDir; @@ -105,7 +107,7 @@ public class DirectoriesTest private static Set CFM; private static Map> sstablesByTableName; - @Parameterized.Parameter(0) + @Parameterized.Parameter public SSTableId.Builder idBuilder; @Parameterized.Parameter(1) @@ -151,7 +153,7 @@ public class DirectoriesTest @AfterClass public static void afterClass() { - FileUtils.deleteRecursive(tempDataDir); + tempDataDir.deleteRecursive(); } private static DataDirectory[] toDataDirectories(File location) @@ -159,7 +161,7 @@ public class DirectoriesTest return new DataDirectory[] { new DataDirectory(location) }; } - private void createTestFiles() throws IOException + private void createTestFiles() { for (TableMetadata cfm : CFM) { @@ -181,25 +183,27 @@ public class DirectoriesTest } } - class FakeSnapshot { + static class FakeSnapshot { final TableMetadata table; final String tag; final File snapshotDir; final SnapshotManifest manifest; + final boolean ephemeral; - FakeSnapshot(TableMetadata table, String tag, File snapshotDir, SnapshotManifest manifest) + FakeSnapshot(TableMetadata table, String tag, File snapshotDir, SnapshotManifest manifest, boolean ephemeral) { this.table = table; this.tag = tag; this.snapshotDir = snapshotDir; this.manifest = manifest; + this.ephemeral = ephemeral; } public TableSnapshot asTableSnapshot() { Instant createdAt = manifest == null ? null : manifest.createdAt; Instant expiresAt = manifest == null ? null : manifest.expiresAt; - return new TableSnapshot(table.keyspace, table.name, table.id.asUUID(), tag, createdAt, expiresAt, Collections.singleton(snapshotDir)); + return new TableSnapshot(table.keyspace, table.name, table.id.asUUID(), tag, createdAt, expiresAt, Collections.singleton(snapshotDir), ephemeral); } } @@ -211,7 +215,7 @@ public class DirectoriesTest .build(); } - public FakeSnapshot createFakeSnapshot(TableMetadata table, String tag, boolean createManifest) throws IOException + public FakeSnapshot createFakeSnapshot(TableMetadata table, String tag, boolean createManifest, boolean ephemeral) throws IOException { File tableDir = cfDir(table); tableDir.tryCreateDirectories(); @@ -225,11 +229,15 @@ public class DirectoriesTest if (createManifest) { File manifestFile = Directories.getSnapshotManifestFile(snapshotDir); - manifest = new SnapshotManifest(Collections.singletonList(sstableDesc.filenameFor(Component.DATA)), new DurationSpec.IntSecondsBound("1m"), now()); + manifest = new SnapshotManifest(Collections.singletonList(sstableDesc.filenameFor(Component.DATA)), new DurationSpec.IntSecondsBound("1m"), now(), ephemeral); manifest.serializeToJsonFile(manifestFile); } + else if (ephemeral) + { + Files.createFile(snapshotDir.toPath().resolve("ephemeral.snapshot")); + } - return new FakeSnapshot(table, tag, snapshotDir, manifest); + return new FakeSnapshot(table, tag, snapshotDir, manifest, ephemeral); } private List createFakeSSTable(File dir, String cf, int gen) @@ -269,7 +277,7 @@ public class DirectoriesTest } @Test - public void testStandardDirs() throws IOException + public void testStandardDirs() { for (TableMetadata cfm : CFM) { @@ -296,22 +304,27 @@ public class DirectoriesTest assertThat(directories.listSnapshots()).isEmpty(); // Create snapshot with and without manifest - FakeSnapshot snapshot1 = createFakeSnapshot(fakeTable, SNAPSHOT1, true); - FakeSnapshot snapshot2 = createFakeSnapshot(fakeTable, SNAPSHOT2, false); + FakeSnapshot snapshot1 = createFakeSnapshot(fakeTable, SNAPSHOT1, true, false); + FakeSnapshot snapshot2 = createFakeSnapshot(fakeTable, SNAPSHOT2, false, false); + // ephemeral without manifst + FakeSnapshot snapshot3 = createFakeSnapshot(fakeTable, SNAPSHOT3, false, true); // Both snapshots should be present Map snapshots = directories.listSnapshots(); - assertThat(snapshots.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT1, SNAPSHOT2)); + assertThat(snapshots.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT1, SNAPSHOT2, SNAPSHOT3)); assertThat(snapshots.get(SNAPSHOT1)).isEqualTo(snapshot1.asTableSnapshot()); assertThat(snapshots.get(SNAPSHOT2)).isEqualTo(snapshot2.asTableSnapshot()); + assertThat(snapshots.get(SNAPSHOT3)).isEqualTo(snapshot3.asTableSnapshot()); // Now remove snapshot1 - FileUtils.deleteRecursive(snapshot1.snapshotDir); + snapshot1.snapshotDir.deleteRecursive(); - // Only snapshot 2 should be present + // Only snapshot 2 and 3 should be present snapshots = directories.listSnapshots(); - assertThat(snapshots.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT2)); + assertThat(snapshots.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT2, SNAPSHOT3)); assertThat(snapshots.get(SNAPSHOT2)).isEqualTo(snapshot2.asTableSnapshot()); + assertThat(snapshots.get(SNAPSHOT3)).isEqualTo(snapshot3.asTableSnapshot()); + assertThat(snapshots.get(SNAPSHOT3).isEphemeral()).isTrue(); } @Test @@ -322,21 +335,23 @@ public class DirectoriesTest assertThat(directories.listSnapshotDirsByTag()).isEmpty(); // Create snapshot with and without manifest - FakeSnapshot snapshot1 = createFakeSnapshot(fakeTable, SNAPSHOT1, true); - FakeSnapshot snapshot2 = createFakeSnapshot(fakeTable, SNAPSHOT2, false); + FakeSnapshot snapshot1 = createFakeSnapshot(fakeTable, SNAPSHOT1, true, false); + FakeSnapshot snapshot2 = createFakeSnapshot(fakeTable, SNAPSHOT2, false, false); + FakeSnapshot snapshot3 = createFakeSnapshot(fakeTable, SNAPSHOT3, false, true); // Both snapshots should be present Map> snapshotDirs = directories.listSnapshotDirsByTag(); - assertThat(snapshotDirs.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT1, SNAPSHOT2)); + assertThat(snapshotDirs.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT1, SNAPSHOT2, SNAPSHOT3)); assertThat(snapshotDirs.get(SNAPSHOT1)).allMatch(snapshotDir -> snapshotDir.equals(snapshot1.snapshotDir)); assertThat(snapshotDirs.get(SNAPSHOT2)).allMatch(snapshotDir -> snapshotDir.equals(snapshot2.snapshotDir)); + assertThat(snapshotDirs.get(SNAPSHOT3)).allMatch(snapshotDir -> snapshotDir.equals(snapshot3.snapshotDir)); // Now remove snapshot1 - FileUtils.deleteRecursive(snapshot1.snapshotDir); + snapshot1.snapshotDir.deleteRecursive(); - // Only snapshot 2 should be present + // Only snapshot 2 and 3 should be present snapshotDirs = directories.listSnapshotDirsByTag(); - assertThat(snapshotDirs.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT2)); + assertThat(snapshotDirs.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT2, SNAPSHOT3)); } @Test @@ -353,7 +368,7 @@ public class DirectoriesTest File manifestFile = directories.getSnapshotManifestFile(tag); - SnapshotManifest manifest = new SnapshotManifest(files, new DurationSpec.IntSecondsBound("1m"), now()); + SnapshotManifest manifest = new SnapshotManifest(files, new DurationSpec.IntSecondsBound("1m"), now(), false); manifest.serializeToJsonFile(manifestFile); Set dirs = new HashSet<>(); @@ -488,7 +503,7 @@ public class DirectoriesTest } @Test - public void testTemporaryFile() throws IOException + public void testTemporaryFile() { for (TableMetadata cfm : CFM) { @@ -552,11 +567,10 @@ public class DirectoriesTest final Directories directories = new Directories(cfm, toDataDirectories(tempDataDir)); assertEquals(cfDir(cfm), directories.getDirectoryForNewSSTables()); final String n = Long.toString(nanoTime()); - Callable directoryGetter = new Callable() { - public File call() throws Exception { - Descriptor desc = new Descriptor(cfDir(cfm), KS, cfm.name, sstableId(1), SSTableFormat.Type.BIG); - return Directories.getSnapshotDirectory(desc, n); - } + Callable directoryGetter = () -> + { + Descriptor desc = new Descriptor(cfDir(cfm), KS, cfm.name, sstableId(1), SSTableFormat.Type.BIG); + return Directories.getSnapshotDirectory(desc, n); }; List> invoked = Executors.newFixedThreadPool(2).invokeAll(Arrays.asList(directoryGetter, directoryGetter)); for(Future fut:invoked) { diff --git a/test/unit/org/apache/cassandra/service/snapshot/SnapshotLoaderTest.java b/test/unit/org/apache/cassandra/service/snapshot/SnapshotLoaderTest.java index 21ab7d9b11..a4a6ad2bad 100644 --- a/test/unit/org/apache/cassandra/service/snapshot/SnapshotLoaderTest.java +++ b/test/unit/org/apache/cassandra/service/snapshot/SnapshotLoaderTest.java @@ -27,6 +27,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; +import org.junit.Assert; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -123,9 +124,40 @@ public class SnapshotLoaderTest Paths.get(baseDir.toString(), DATA_DIR_3))); Set 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)); + assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_1, TABLE1_NAME, TABLE1_ID, TAG1, null, null, tag1Files, false)); + assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_1, TABLE2_NAME, TABLE2_ID, TAG2, null, null, tag2Files, false)); + assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_2, TABLE3_NAME, TABLE3_ID, TAG3, null, null, tag3Files, false)); + } + + @Test + public void testEphemeralSnapshotWithoutManifest() throws IOException + { + Set tag1Files = new HashSet<>(); + + // Create one snapshot per table - without manifests: + // - ks1.t1 : tag1 + File baseDir = new File(tmpDir.newFolder()); + boolean ephemeralFileCreated = false; + for (String dataDir : DATA_DIRS) + { + File dir = createDir(baseDir, dataDir, KEYSPACE_1, tableDirName(TABLE1_NAME, TABLE1_ID), Directories.SNAPSHOT_SUBDIR, TAG1); + tag1Files.add(dir); + if (!ephemeralFileCreated) + { + createEphemeralMarkerFile(dir); + ephemeralFileCreated = true; + } + } + + // Verify snapshot is 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 snapshots = loader.loadSnapshots(); + assertThat(snapshots).hasSize(1); + assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_1, TABLE1_NAME, TABLE1_ID, TAG1, null, null, tag1Files, true)); + Assert.assertTrue(snapshots.stream().findFirst().get().isEphemeral()); } @Test @@ -169,9 +201,9 @@ public class SnapshotLoaderTest Paths.get(baseDir.toString(), DATA_DIR_3))); Set 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)); + assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_1, TABLE1_NAME, TABLE1_ID, TAG1, tag1Ts, null, tag1Files, false)); + assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_1, TABLE2_NAME, TABLE2_ID, TAG2, tag2Ts, tag2Ts.plusSeconds(tag2Ttl.toSeconds()), tag2Files, false)); + assertThat(snapshots).contains(new TableSnapshot(KEYSPACE_2, TABLE3_NAME, TABLE3_ID, TAG3, tag3Ts, null, tag3Files, false)); } @Test @@ -208,7 +240,7 @@ public class SnapshotLoaderTest private void writeManifest(File snapshotDir, Instant creationTime, DurationSpec.IntSecondsBound ttl) throws IOException { - SnapshotManifest manifest = new SnapshotManifest(Lists.newArrayList("f1", "f2", "f3"), ttl, creationTime); + SnapshotManifest manifest = new SnapshotManifest(Lists.newArrayList("f1", "f2", "f3"), ttl, creationTime, false); manifest.serializeToJsonFile(getManifestFile(snapshotDir)); } @@ -219,6 +251,11 @@ public class SnapshotLoaderTest return file; } + private static void createEphemeralMarkerFile(File dir) + { + Assert.assertTrue(new File(dir, "ephemeral.snapshot").createFileIfNotExists()); + } + static String tableDirName(String tableName, UUID tableId) { return String.format("%s-%s", tableName, removeDashes(tableId)); diff --git a/test/unit/org/apache/cassandra/service/snapshot/SnapshotManagerTest.java b/test/unit/org/apache/cassandra/service/snapshot/SnapshotManagerTest.java index 0c078d47da..eeb3b63fc3 100644 --- a/test/unit/org/apache/cassandra/service/snapshot/SnapshotManagerTest.java +++ b/test/unit/org/apache/cassandra/service/snapshot/SnapshotManagerTest.java @@ -50,7 +50,7 @@ public class SnapshotManagerTest @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder(); - private TableSnapshot generateSnapshotDetails(String tag, Instant expiration) throws Exception { + private TableSnapshot generateSnapshotDetails(String tag, Instant expiration, boolean ephemeral) throws Exception { return new TableSnapshot( "ks", "tbl", @@ -58,15 +58,16 @@ public class SnapshotManagerTest tag, Instant.EPOCH, expiration, - createFolders(temporaryFolder) + createFolders(temporaryFolder), + ephemeral ); } @Test public void testLoadSnapshots() throws Exception { - TableSnapshot expired = generateSnapshotDetails("expired", Instant.EPOCH); - TableSnapshot nonExpired = generateSnapshotDetails("non-expired", now().plusSeconds(ONE_DAY_SECS)); - TableSnapshot nonExpiring = generateSnapshotDetails("non-expiring", null); + TableSnapshot expired = generateSnapshotDetails("expired", Instant.EPOCH, false); + TableSnapshot nonExpired = generateSnapshotDetails("non-expired", now().plusSeconds(ONE_DAY_SECS), false); + TableSnapshot nonExpiring = generateSnapshotDetails("non-expiring", null, false); List snapshots = Arrays.asList(expired, nonExpired, nonExpiring); // Create SnapshotManager with 3 snapshots: expired, non-expired and non-expiring @@ -84,9 +85,9 @@ public class SnapshotManagerTest SnapshotManager manager = new SnapshotManager(3, 3); // Add 3 snapshots: expired, non-expired and non-expiring - TableSnapshot expired = generateSnapshotDetails("expired", Instant.EPOCH); - TableSnapshot nonExpired = generateSnapshotDetails("non-expired", now().plusMillis(ONE_DAY_SECS)); - TableSnapshot nonExpiring = generateSnapshotDetails("non-expiring", null); + TableSnapshot expired = generateSnapshotDetails("expired", Instant.EPOCH, false); + TableSnapshot nonExpired = generateSnapshotDetails("non-expired", now().plusMillis(ONE_DAY_SECS), false); + TableSnapshot nonExpiring = generateSnapshotDetails("non-expiring", null, false); manager.addSnapshot(expired); manager.addSnapshot(nonExpired); manager.addSnapshot(nonExpiring); @@ -118,8 +119,8 @@ public class SnapshotManagerTest // Add 2 expiring snapshots: one to expire in 2 seconds, another in 1 day int TTL_SECS = 2; - TableSnapshot toExpire = generateSnapshotDetails("to-expire", now().plusSeconds(TTL_SECS)); - TableSnapshot nonExpired = generateSnapshotDetails("non-expired", now().plusMillis(ONE_DAY_SECS)); + TableSnapshot toExpire = generateSnapshotDetails("to-expire", now().plusSeconds(TTL_SECS), false); + TableSnapshot nonExpired = generateSnapshotDetails("non-expired", now().plusMillis(ONE_DAY_SECS), false); manager.addSnapshot(toExpire); manager.addSnapshot(nonExpired); @@ -150,7 +151,7 @@ public class SnapshotManagerTest { // Given SnapshotManager manager = new SnapshotManager(1, 3); - TableSnapshot expiringSnapshot = generateSnapshotDetails("snapshot", now().plusMillis(50000)); + TableSnapshot expiringSnapshot = generateSnapshotDetails("snapshot", now().plusMillis(50000), false); manager.addSnapshot(expiringSnapshot); assertThat(manager.getExpiringSnapshots()).contains(expiringSnapshot); assertThat(expiringSnapshot.exists()).isTrue(); diff --git a/test/unit/org/apache/cassandra/service/snapshot/SnapshotManifestTest.java b/test/unit/org/apache/cassandra/service/snapshot/SnapshotManifestTest.java index d3b11c0643..5eac6a2b78 100644 --- a/test/unit/org/apache/cassandra/service/snapshot/SnapshotManifestTest.java +++ b/test/unit/org/apache/cassandra/service/snapshot/SnapshotManifestTest.java @@ -108,7 +108,7 @@ public class SnapshotManifestTest @Test public void testSerializeAndDeserialize() throws Exception { - SnapshotManifest manifest = new SnapshotManifest(Arrays.asList("db1", "db2", "db3"), new DurationSpec.IntSecondsBound("2m"), Instant.ofEpochMilli(currentTimeMillis())); + SnapshotManifest manifest = new SnapshotManifest(Arrays.asList("db1", "db2", "db3"), new DurationSpec.IntSecondsBound("2m"), Instant.ofEpochMilli(currentTimeMillis()), false); File manifestFile = new File(tempFolder.newFile("manifest.json")); manifest.serializeToJsonFile(manifestFile); diff --git a/test/unit/org/apache/cassandra/service/snapshot/TableSnapshotTest.java b/test/unit/org/apache/cassandra/service/snapshot/TableSnapshotTest.java index 4bb1756c31..c1614dffdc 100644 --- a/test/unit/org/apache/cassandra/service/snapshot/TableSnapshotTest.java +++ b/test/unit/org/apache/cassandra/service/snapshot/TableSnapshotTest.java @@ -74,7 +74,9 @@ public class TableSnapshotTest "some", null, null, - folders); + folders, + false + ); assertThat(snapshot.exists()).isTrue(); @@ -95,7 +97,9 @@ public class TableSnapshotTest "some", null, null, - folders); + folders, + false + ); assertThat(snapshot.isExpiring()).isFalse(); assertThat(snapshot.isExpired(now())).isFalse(); @@ -107,7 +111,9 @@ public class TableSnapshotTest "some", now(), null, - folders); + folders, + false + ); assertThat(snapshot.isExpiring()).isFalse(); assertThat(snapshot.isExpired(now())).isFalse(); @@ -119,7 +125,9 @@ public class TableSnapshotTest "some", now(), now().plusSeconds(1000), - folders); + folders, + false + ); assertThat(snapshot.isExpiring()).isTrue(); assertThat(snapshot.isExpired(now())).isFalse(); @@ -131,7 +139,8 @@ public class TableSnapshotTest "some", now(), now().minusSeconds(1000), - folders); + folders, + false); assertThat(snapshot.isExpiring()).isTrue(); assertThat(snapshot.isExpired(now())).isTrue(); @@ -159,7 +168,8 @@ public class TableSnapshotTest "some", null, null, - folders); + folders, + false); Long res = 0L; @@ -185,7 +195,9 @@ public class TableSnapshotTest "some", null, null, - folders); + folders, + false + ); Long res = 0L; @@ -214,7 +226,10 @@ public class TableSnapshotTest "some1", createdAt, null, - folders); + folders, + false + ); + assertThat(withCreatedAt.getCreatedAt()).isEqualTo(createdAt); // When createdAt is null, it should return the snapshot folder minimum update time @@ -225,7 +240,10 @@ public class TableSnapshotTest "some1", null, null, - folders); + folders, + false + ); + assertThat(withoutCreatedAt.getCreatedAt()).isEqualTo(Instant.ofEpochMilli(folders.stream().mapToLong(f -> f.lastModified()).min().getAsLong())); }