diff --git a/CHANGES.txt b/CHANGES.txt index ee4e28789ec0..97c638682119 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,8 @@ 5.1 + * Enable filtering of snapshots on keyspace, table and snapshot name in nodetool listsnapshots (CASSANDRA-20151) * Create manifest upon loading where it does not exist or enrich it (CASSANDRA-20150) * Propagate true size of snapshot in SnapshotDetailsTabularData to not call JMX twice in nodetool listsnapshots (CASSANDRA-20149) - * Implementation of CEP-43 (CASSANDRA-19964) + * Implementation of CEP-43 - copying a table via CQL by CREATE TABLE LIKE (CASSANDRA-19964) * Periodically disconnect roles that are revoked or have LOGIN=FALSE set (CASSANDRA-19385) * AST library for CQL-based fuzz tests (CASSANDRA-20198) * Support audit logging for JMX operations (CASSANDRA-20128) diff --git a/src/java/org/apache/cassandra/service/snapshot/SnapshotManagerMBean.java b/src/java/org/apache/cassandra/service/snapshot/SnapshotManagerMBean.java index 214adcc4454f..332ebb4b5480 100644 --- a/src/java/org/apache/cassandra/service/snapshot/SnapshotManagerMBean.java +++ b/src/java/org/apache/cassandra/service/snapshot/SnapshotManagerMBean.java @@ -55,7 +55,19 @@ public interface SnapshotManagerMBean void clearSnapshot(String tag, Map options, String... keyspaceNames) throws IOException; /** - * Get the details of all the snapshots + * Get the details of all the snapshots. Options might be: + * + *
+     * no_ttl: "true" or "false"
+     * include_ephemeral: "true" or "false"
+     * keyspace: name of keyspace to get snapshots of
+     * table: name of table to get tables of
+     * snapshot: name of snapshot to list
+     * 
+ * + * There is no requirement as what option has to be specified. + * Values of 'null' for keyspace, table or snapshot do not have any effect / will + * not be part of the filtering. * * @param options map of options used for filtering of snapshots * @return A map of snapshotName to all its details in Tabular form. diff --git a/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java b/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java index e1b61d4b518b..97ea809fdd9a 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java +++ b/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java @@ -44,6 +44,21 @@ public class ListSnapshots extends NodeToolCmd description = "Include ephememeral snapshots") private boolean includeEphemeral = false; + @Option(title = "keyspace", + name = { "-k", "--keyspace" }, + description = "Include snapshots of specified keyspace name") + private String keyspace = null; + + @Option(title = "table", + name = { "-t", "--table" }, + description = "Include snapshots of specified table name") + private String table = null; + + @Option(title = "snapshot", + name = { "-n", "--snapshot"}, + description = "Include snapshots of specified name") + private String snapshotName = null; + @Override public void execute(NodeProbe probe) { @@ -55,6 +70,12 @@ public void execute(NodeProbe probe) Map options = new HashMap<>(); options.put("no_ttl", Boolean.toString(noTTL)); options.put("include_ephemeral", Boolean.toString(includeEphemeral)); + if (keyspace != null) + options.put("keyspace", keyspace); + if (table != null) + options.put("table", table); + if (snapshotName != null) + options.put("snapshot", snapshotName); final Map snapshotDetails = probe.getSnapshotDetails(options); if (snapshotDetails.isEmpty()) diff --git a/test/distributed/org/apache/cassandra/distributed/test/SnapshotsTest.java b/test/distributed/org/apache/cassandra/distributed/test/SnapshotsTest.java index f04a3403cad1..d0b78112a8d5 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/SnapshotsTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/SnapshotsTest.java @@ -512,6 +512,49 @@ public void testTakingSnapshoWithSameNameOnDifferentTablesDoesNotFail() cluster.get(1).nodetoolResult("snapshot", "-t", "somename", "-kt", String.format("%s.tbl2", KEYSPACE)).asserts().success(); } + @Test + public void testListingOfSnapshotsByKeyspaceAndTable() + { + IInvokableInstance instance = cluster.get(1); + cluster.schemaChange("CREATE KEYSPACE IF NOT EXISTS ks1 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};"); + cluster.schemaChange("CREATE KEYSPACE IF NOT EXISTS ks2 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};"); + cluster.schemaChange("CREATE TABLE IF NOT EXISTS ks1.tbl (key int, value text, PRIMARY KEY (key))"); + cluster.schemaChange("CREATE TABLE IF NOT EXISTS ks1.tbl2 (key int, value text, PRIMARY KEY (key))"); + cluster.schemaChange("CREATE TABLE IF NOT EXISTS ks2.tbl (key int, value text, PRIMARY KEY (key))"); + cluster.schemaChange("CREATE TABLE IF NOT EXISTS ks2.tbl2 (key int, value text, PRIMARY KEY (key))"); + + populate(cluster, "ks1", "tbl"); + populate(cluster, "ks1", "tbl2"); + populate(cluster, "ks2", "tbl"); + populate(cluster, "ks2", "tbl2"); + + instance.nodetoolResult("snapshot", "-t", "tagks1tbl", "-kt", "ks1.tbl").asserts().success(); + instance.nodetoolResult("snapshot", "-t", "tagks1tbl2", "-kt", "ks1.tbl2").asserts().success(); + instance.nodetoolResult("snapshot", "-t", "tagks2tbl", "-kt", "ks2.tbl").asserts().success(); + instance.nodetoolResult("snapshot", "-t", "tagks2tbl2", "-kt", "ks2.tbl2").asserts().success(); + + waitForSnapshot("ks1", null, "tagks1tbl", true, false); + waitForSnapshot("ks1", null, "tagks1tbl2", true, false); + waitForSnapshot("ks1", null, "tagks2tbl", false, false); + waitForSnapshot("ks1", null, "tagks2tbl2", false, false); + + waitForSnapshot("ks1", "tbl", "tagks1tbl", true, false); + waitForSnapshot("ks1", "tbl", "tagks1tbl2", false, false); + waitForSnapshot("ks1", "tbl", "tagks2tbl", false, false); + waitForSnapshot("ks1", "tbl", "tagks2tbl2", false, false); + + waitForSnapshot(null, "tbl", "tagks1tbl", true, false); + waitForSnapshot(null, "tbl", "tagks1tbl2", false, false); + waitForSnapshot(null, "tbl", "tagks2tbl", true, false); + waitForSnapshot(null, "tbl", "tagks2tbl2", false, false); + + NodeToolResult nodeToolResult = instance.nodetoolResult("listsnapshots", "-n", "tagks1tbl"); + nodeToolResult.asserts().success(); + List snapshots = extractSnapshots(nodeToolResult.getStdout()); + assertEquals(1, snapshots.size()); + assertTrue(snapshots.get(0).contains("tagks1tbl")); + } + private void populate(Cluster cluster) { for (int i = 0; i < 100; i++) @@ -555,13 +598,27 @@ private boolean waitForSnapshotInternal(String keyspaceName, String tableName, S if (noTTL) args.add("-nt"); + if (keyspaceName != null) + { + args.add("-k"); + args.add(keyspaceName); + } + + if (tableName != null) + { + args.add("-t"); + args.add(tableName); + } + + if (snapshotName != null) + { + args.add("-n"); + args.add(snapshotName); + } + listsnapshots = cluster.get(1).nodetoolResult(args.toArray(new String[0])); - List lines = Arrays.stream(listsnapshots.getStdout().split("\n")) - .filter(line -> !line.isEmpty()) - .filter(line -> !line.startsWith("Snapshot Details:") && !line.startsWith("There are no snapshots")) - .filter(line -> !line.startsWith("Snapshot name") && !line.startsWith("Total TrueDiskSpaceUsed")) - .collect(toList()); + List lines = extractSnapshots(listsnapshots.getStdout()); return expectPresent == lines.stream().anyMatch(line -> line.startsWith(snapshotName)); } @@ -632,4 +689,15 @@ else if (LOCAL_SYSTEM_KEYSPACE_NAMES.contains(keyspace.getName()) || REPLICATED_ return result.toArray(new String[0]); }, forSystemKeyspaces); } + + private List extractSnapshots(String listSnapshotsStdOut) + { + return Arrays.stream(listSnapshotsStdOut.split("\n")) + .filter(line -> !line.isEmpty()) + .filter(line -> !line.startsWith("Snapshot Details:")) + .filter(line -> !line.startsWith("There are no snapshots")) + .filter(line -> !line.startsWith("Snapshot name")) + .filter(line -> !line.startsWith("Total TrueDiskSpaceUsed")) + .collect(toList()); + } }