blob: 2de8f54694bc560e9e56493517c6190112aa085c [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.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 snapshotName2 = "second-snapshot";
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<String, String[]> initialisationData = initialise(c);
rewriteManifestToEphemeral(initialisationData.left, initialisationData.right);
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<String, String[]> 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));
}
}
@Test
public void testEphemeralSnapshotIsNotClearableFromNodetool() throws Exception
{
try (Cluster c = init(builder().withNodes(1)
.withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL))
.start()))
{
IInvokableInstance instance = c.get(1);
Pair<String, String[]> initialisationData = initialise(c);
rewriteManifestToEphemeral(initialisationData.left, initialisationData.right);
assertTrue(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName));
instance.nodetoolResult("clearsnapshot", "-t", snapshotName).asserts().success();
// ephemeral snapshot was not removed as it can not be (from nodetool / user operation)
assertTrue(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName));
assertFalse(instance.logs().grep("Skipping deletion of ephemeral snapshot 'snapshotname' in keyspace distributed_test_keyspace. " +
"Ephemeral snapshots are not removable by a user.").getResult().isEmpty());
}
}
@Test
public void testClearingAllSnapshotsFromNodetoolWillKeepEphemeralSnaphotsIntact() throws Exception
{
try (Cluster c = init(builder().withNodes(1)
.withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL))
.start()))
{
IInvokableInstance instance = c.get(1);
Pair<String, String[]> initialisationData = initialise(c);
rewriteManifestToEphemeral(initialisationData.left, initialisationData.right);
instance.nodetoolResult("clearsnapshot", "--all").asserts().success();
assertTrue(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName));
assertFalse(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName2));
}
}
private Pair<String, String[]> 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);
// take one more snapshot, this one is not ephemeral,
// starting Cassandra will clear ephemerals, but it will not affect non-ephemeral snapshots
assertEquals(0, instance.nodetool("snapshot", "-kt", withKeyspace("%s." + tableName), "-t", snapshotName2));
waitForSnapshot(instance, snapshotName2);
String tableId = instance.callOnInstance((IIsolatedExecutor.SerializableCallable<String>) () -> {
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 snapshot
instance.startup();
assertFalse(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName));
assertTrue(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName2));
}
private void waitForSnapshot(IInvokableInstance instance, String snapshotName)
{
await().timeout(20, SECONDS)
.pollInterval(1, SECONDS)
.until(() -> instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName));
}
private void rewriteManifestToEphemeral(String tableId, String[] dataDirs) throws Exception
{
// 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));
}
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!");
}
}