Skip to content

Commit c9d60a6

Browse files
nvharikrishnabeobal
authored andcommitted
Offline tool for working with cluster metadata dump files
The following subcommands have been added: * abortbootstrap Cancel and undo an inflight bootstrap * abortmove Cancel and undo an inflight token move * abortdecommission Cancel and undo an inflight decommission * assassinate Remove a node from cluster metadata * resetcms Redefine the CMS membership * move Modify a node's (single) owned token * describe Output a summary of the cluster metadata * forcejoin Force a registered or joining node into a JOINED state * print Output a full rendering of cluster metadata * printdirectory Output a full rendering of the directory * printdataplacements Output the full data placements Patch by Venkata Harikrishna Nukala; reviewed by Alex Petrov and Sam Tunnicliffe for CASSANDRA-19151
1 parent 4930ae0 commit c9d60a6

9 files changed

Lines changed: 2968 additions & 4 deletions

File tree

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
6.0-alpha2
2+
* Add an offline cluster metadata tool (CASSANDRA-19151)
23
* Accord: Tail Latency Improvements (CASSANDRA-21361)
34
* Artificial Latency Injection (CASSANDRA-17024)
45
* Accord: Clean Shutdown/Restart, Rebootstrap, et al (CASSANDRA-21355)

src/java/org/apache/cassandra/tools/CMSOfflineTool.java

Lines changed: 825 additions & 0 deletions
Large diffs are not rendered by default.

src/java/org/apache/cassandra/tools/nodetool/CMSAdmin.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package org.apache.cassandra.tools.nodetool;
2020

21+
import java.io.IOException;
2122
import java.io.PrintStream;
2223
import java.util.ArrayList;
2324
import java.util.Comparator;
@@ -28,9 +29,11 @@
2829
import com.google.common.collect.ImmutableList;
2930

3031
import org.apache.cassandra.tcm.Epoch;
32+
import org.apache.cassandra.tcm.serialization.Version;
3133
import org.apache.cassandra.tools.NodeProbe;
3234
import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
3335

36+
import picocli.CommandLine.ArgGroup;
3437
import picocli.CommandLine.Command;
3538
import picocli.CommandLine.Option;
3639
import picocli.CommandLine.Parameters;
@@ -53,6 +56,7 @@
5356
CMSAdmin.Snapshot.class,
5457
CMSAdmin.Unregister.class,
5558
CMSAdmin.AbortInitialization.class,
59+
CMSAdmin.DumpClusterMetadata.class,
5660
CMSAdmin.DumpDirectory.class,
5761
CMSAdmin.DumpLog.class,
5862
CMSAdmin.ResumeDropAccordTable.class })
@@ -164,7 +168,7 @@ protected void execute(NodeProbe probe)
164168
if (!rf.contains(":"))
165169
{
166170
if (args.size() > 1)
167-
throw new IllegalArgumentException("Simple placement can only specify a single replication factor accross all data centers");
171+
throw new IllegalArgumentException("Simple placement can only specify a single replication factor across all data centers");
168172
int parsedRf;
169173
try
170174
{
@@ -295,4 +299,55 @@ public void execute(NodeProbe probe)
295299
probe.getCMSOperationsProxy().resumeDropAccordTable(tableId);
296300
}
297301
}
302+
303+
@Command(name = "dump", description = "Dumps cluster metadata into a file")
304+
public static class DumpClusterMetadata extends AbstractCommand
305+
{
306+
@ArgGroup(exclusive = false, multiplicity = "0..1")
307+
DumpOptions dumpOptions;
308+
309+
static class DumpOptions
310+
{
311+
@Option(names = { "-e", "--epoch" }, paramLabel = "epoch",
312+
description = "Epoch at which cluster metadata should be dumped", required = true)
313+
Long epoch;
314+
315+
@Option(names = { "-te", "--transform-epoch" }, paramLabel = "transform_epoch",
316+
description = "Force metadata to given X epoch while dumping", required = true)
317+
Long transformEpoch;
318+
319+
@Option(names = { "-sv", "--serialization-version" }, paramLabel = "serialization_version",
320+
description = "Serialization Version", required = true)
321+
Version version;
322+
}
323+
324+
@Override
325+
protected void execute(NodeProbe probe)
326+
{
327+
try
328+
{
329+
if (dumpOptions == null)
330+
{
331+
String fileLocation = probe.getCMSOperationsProxy().dumpClusterMetadata();
332+
printCMSDumpLocation(probe, fileLocation);
333+
}
334+
else
335+
{
336+
String fileLocation = probe.getCMSOperationsProxy().dumpClusterMetadata(dumpOptions.epoch,
337+
dumpOptions.transformEpoch,
338+
dumpOptions.version.name());
339+
printCMSDumpLocation(probe, fileLocation);
340+
}
341+
}
342+
catch (IOException e)
343+
{
344+
throw new RuntimeException(e);
345+
}
346+
}
347+
348+
private void printCMSDumpLocation(NodeProbe probe, String fileLocation)
349+
{
350+
probe.output().out.println("Cluster Metadata dump available at " + fileLocation);
351+
}
352+
}
298353
}

test/distributed/org/apache/cassandra/distributed/test/log/ClusterMetadataDumpTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
import org.apache.cassandra.distributed.Cluster;
2626
import org.apache.cassandra.distributed.api.NodeToolResult;
2727
import org.apache.cassandra.distributed.test.TestBaseImpl;
28+
import org.apache.cassandra.io.util.File;
29+
import org.apache.cassandra.tcm.ClusterMetadata;
2830
import org.apache.cassandra.tcm.ClusterMetadataService;
31+
import org.apache.cassandra.tcm.membership.NodeVersion;
2932
import org.apache.cassandra.tcm.transformations.CustomTransformation;
3033

3134
import static org.junit.Assert.assertEquals;
@@ -113,4 +116,59 @@ public void dumpDirectoryTest() throws IOException
113116
assertEquals(3, tokensFound);
114117
}
115118
}
119+
120+
@Test
121+
public void dumpClusterMetadataTest() throws IOException
122+
{
123+
try (Cluster cluster = init(builder().withNodes(3)
124+
.start()))
125+
{
126+
NodeToolResult res = cluster.get(1).nodetoolResult("cms", "dump");
127+
res.asserts().success();
128+
129+
String stdout = res.getStdout();
130+
String expectedMsgPrefix = "Cluster Metadata dump available at ";
131+
assertTrue(stdout.contains(expectedMsgPrefix));
132+
int index = stdout.indexOf(expectedMsgPrefix);
133+
String dumpFile = stdout.substring(index + expectedMsgPrefix.length()).trim();
134+
assertTrue(new File(dumpFile).exists());
135+
}
136+
}
137+
138+
@Test
139+
public void dumpClusterMetadataWithParamsTest() throws IOException
140+
{
141+
try (Cluster cluster = init(builder().withNodes(3)
142+
.start()))
143+
{
144+
long currentEpoch = cluster.get(1).callOnInstance(() -> ClusterMetadata.current().epoch.getEpoch());
145+
String serVersion = NodeVersion.CURRENT.serializationVersion().toString();
146+
147+
NodeToolResult res = cluster.get(1).nodetoolResult("cms", "dump",
148+
"--epoch", "1",
149+
"--transform-epoch", String.valueOf(currentEpoch),
150+
"--serialization-version", serVersion);
151+
res.asserts().success();
152+
153+
String stdout = res.getStdout();
154+
String expectedMsgPrefix = "Cluster Metadata dump available at ";
155+
assertTrue(stdout.contains(expectedMsgPrefix));
156+
int index = stdout.indexOf(expectedMsgPrefix);
157+
String dumpFile = stdout.substring(index + expectedMsgPrefix.length()).trim();
158+
assertTrue(new File(dumpFile).exists());
159+
}
160+
}
161+
162+
@Test
163+
public void dumpClusterMetadataWithPartialParamsFailsTest() throws IOException
164+
{
165+
try (Cluster cluster = init(builder().withNodes(3)
166+
.start()))
167+
{
168+
// Providing only --epoch without --transform-epoch and --serialization-version
169+
// should be rejected by picocli since all three are required together via @ArgGroup
170+
NodeToolResult res = cluster.get(1).nodetoolResult("cms", "dump", "--epoch", "1");
171+
res.asserts().failure();
172+
}
173+
}
116174
}

test/resources/nodetool/help/cms

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ SYNOPSIS
4141
[(-u <username> | --username <username>)] cms abortinitialization
4242
[--initiator <initiator>]
4343

44+
nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)]
45+
[(-pw <password> | --password <password>)]
46+
[(-pwf <passwordFilePath> | --password-file <passwordFilePath>)]
47+
[(-u <username> | --username <username>)] cms dump
48+
[(-e <epoch> | --epoch <epoch>)]
49+
[(-sv <serialization_version> | --serialization-version
50+
<serialization_version>)]
51+
[(-te <transform_epoch> | --transform-epoch <transform_epoch>)]
52+
4453
nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)]
4554
[(-pw <password> | --password <password>)]
4655
[(-pwf <passwordFilePath> | --password-file <passwordFilePath>)]
@@ -102,6 +111,14 @@ COMMANDS
102111

103112
With --initiator option, The address of the node where `cms initialize` was
104113
run.
114+
dump
115+
Dumps cluster metadata into a file
116+
117+
With --epoch option, Epoch at which cluster metadata should be dumped
118+
119+
With --transform-epoch option, Force metadata to given X epoch while dumping
120+
121+
With --serialization-version option, Serialization Version
105122
dumpdirectory
106123
Dump the directory from the current ClusterMetadata
107124

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
NAME
2+
nodetool cms dump - Dumps cluster metadata into a file
3+
4+
SYNOPSIS
5+
nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)]
6+
[(-pw <password> | --password <password>)]
7+
[(-pwf <passwordFilePath> | --password-file <passwordFilePath>)]
8+
[(-u <username> | --username <username>)] cms dump
9+
[(-e <epoch> | --epoch <epoch>)]
10+
[(-sv <serialization_version> | --serialization-version
11+
<serialization_version>)]
12+
[(-te <transform_epoch> | --transform-epoch <transform_epoch>)]
13+
14+
OPTIONS
15+
-e <epoch>, --epoch <epoch>
16+
Epoch at which cluster metadata should be dumped
17+
18+
-h <host>, --host <host>
19+
Node hostname or ip address
20+
21+
-p <port>, --port <port>
22+
Remote jmx agent port number
23+
24+
-pw <password>, --password <password>
25+
Remote jmx agent password
26+
27+
-pwf <passwordFilePath>, --password-file <passwordFilePath>
28+
Path to the JMX password file
29+
30+
-sv <serialization_version>, --serialization-version
31+
<serialization_version>
32+
Serialization Version
33+
34+
-te <transform_epoch>, --transform-epoch <transform_epoch>
35+
Force metadata to given X epoch while dumping
36+
37+
-u <username>, --username <username>
38+
Remote jmx agent username

0 commit comments

Comments
 (0)