cassandra-injvm-dtest
apache/cassandra/.claude/skills/cassandra-injvm-dtest/SKILL.md
Comprehensive guide for writing Apache Cassandra in-JVM distributed tests (dtests). Use when creating tests that simulate multi-node Cassandra clusters within a single JVM for faster integration testing. Covers cluster creation (single-node, multi-node, multi-datacenter), configuration (all cassandra.yaml parameters, features, network topology), instance lifecycle (startup/shutdown/restart), query execution, message filtering for failure scenarios, running code on instances, ClusterUtils utilities, and debugging classloader-related issues (serialization failures, same-class-different-classloader problems).
What's in it
- Apache Cassandra In-JVM DTest Writer
- Overview
- Core Concepts
- Framework Architecture
- Key Classes
- Quick Start
- Basic Cluster Setup
- Test Base Class Pattern
- Cluster Creation Patterns
- Pattern 1: Simple Single-Node Cluster
- Pattern 2: Multi-Node Cluster with Configuration
- Pattern 3: Multi-Datacenter Cluster
- Pattern 4: Cluster with Features
- Pattern 5: Custom Token Distribution
- Pattern 6: Dynamic Port Allocation
- Configuration
- Available Configuration Parameters
- Instance Lifecycle Operations
- Starting Instances
- Stopping Instances
- Restarting Instances
- Adding Instances
- Replacing Instances
- Running Code on Instances
- Pattern 1: Run Without Return Value
- Pattern 2: Get Return Value (Sync)
- Pattern 3: Get Return Value (Async)
- Pattern 4: Pass Parameters
- Pattern 5: Instance-to-Instance Operations
- Query Execution
---
name: cassandra-injvm-dtest
version: "1.0.0"
description: Comprehensive guide for writing Apache Cassandra in-JVM distributed tests (dtests). Use when creating tests that simulate multi-node Cassandra clusters within a single JVM for faster integration testing. Covers cluster creation (single-node, multi-node, multi-datacenter), configuration (all cassandra.yaml parameters, features, network topology), instance lifecycle (startup/shutdown/restart), query execution, message filtering for failure scenarios, running code on instances, ClusterUtils utilities, and debugging classloader-related issues (serialization failures, same-class-different-classloader problems).
---
# Apache Cassandra In-JVM DTest Writer
## Overview
Write comprehensive distributed tests for Apache Cassandra using the in-JVM dtest framework. This framework enables multi-node cluster simulation within a single JVM using isolated classloaders, providing fast and reliable integration testing without actual network communication.
## Core Concepts
### Framework Architecture
The in-JVM dtest framework uses **classloader isolation** to run multiple Cassandra instances in one JVM:
- **Shared ClassLoader**: Common classes (primitives, APIs, config objects, byte buffers)
- **Per-Instance ClassLoader**: Isolated Cassandra internals (DatabaseDescriptor, singletons, all C* code)
- **IsolatedExecutor**: Marshals serialized lambdas across classloader boundaries
- **Message Interception**: Inter-node messaging hooked at MessagingService level (no actual networking)
### Key Classes
| Class | Purpose | Location |
|-------|---------|----------|
| `Cluster` | Main entry point for creating clusters | `org.apache.cassandra.distributed.Cluster` |
| `AbstractCluster` | Core cluster management | `org.apache.cassandra.distributed.impl.AbstractCluster` |
| `Instance` | Individual Cassandra node | `org.apache.cassandra.distributed.impl.Instance` |
| `InstanceConfig` | Node configuration | `org.apache.cassandra.distributed.impl.InstanceConfig` |
| `ClusterUtils` | Utility methods | `org.apache.cassandra.distributed.shared.ClusterUtils` |
| `ICoordinator` | Query execution interface | API package |
| `MessageFilters` | Message filtering/dropping | `org.apache.cassandra.distributed.impl.MessageFilters` |
## Quick Start
### Basic Cluster Setup
```java
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
// Create and start a 3-node cluster
try (Cluster cluster = Cluster.build(3).start()) {
// Create keyspace
cluster.schemaChange("CREATE KEYSPACE test WITH replication = " +
"{'class': 'SimpleStrategy', 'replication_factor': 2}");
// Create table
cluster.schemaChange("CREATE TABLE test.users (id int PRIMARY KEY, name text)");
// Execute query
cluster.coordinator(1).execute("INSERT INTO test.users (id, name) VALUES (?, ?)",
ConsistencyLevel.QUORUM, 1, "Alice");
// Read data
Object[][] rows = cluster.coordinator(1).execute(
"SELECT * FROM test.users WHERE id = ?",
ConsistencyLevel.QUORUM, 1);
}
```
### Test Base Class Pattern
```java
import org.apache.cassandra.distributed.test.TestBaseImpl;
public class MyDTest extends TestBaseImpl {
private static Cluster cluster;
@BeforeClass
public static void setupCluster() throws IOException {
cluster = init(Cluster.build(3).start(), 2); // RF=2
}
@AfterClass
public static void teardownCluster() {
if (cluster != null)
cluster.close();
}
@Test
public void testSomething() {
// Test implementation
}
}
```
## Cluster Creation Patterns
### Pattern 1: Simple Single-Node Cluster
```java
Cluster cluster = Cluster.build(1).start();
```
### Pattern 2: Multi-Node Cluster with Configuration
```java
Cluster cluster = Cluster.build(3)
.withConfig(config -> config
.set("num_tokens", "256")
.set("concurrent_reads", "64")
.set("concurrent_writes", "64"))
.start();
```
### Pattern 3: Multi-Datacenter Cluster
```java
Cluster cluster = builder()
.withRacks(2, 1, 3) // 2 DCs, 1 rack per DC, 3 nodes per rack
.start();
// Or with explicit DC/rack naming
Cluster cluster = builder()
.withDC("dc1", 3)
.withDC("dc2", 3)
.start();
```
### Pattern 4: Cluster with Features
```java
import static org.apache.cassandra.distributed.api.Feature.*;
Cluster cluster = Cluster.build()
.withNodes(3)
.withConfig(config -> config
.with(GOSSIP) // Enable gossip protocol
.with(NETWORK) // Enable inter-node networking
.with(NATIVE_PROTOCOL) // Enable CQL protocol
.with(JMX)) // Enable JMX
.start();
```
### Pattern 5: Custom Token Distribution
```java
import org.apache.cassandra.distributed.api.TokenSupplier;
Cluster cluster = Cluster.build()
.withNodes(3)
.withTokenSupplier(TokenSupplier.evenlyDistributedTokens(3))
.start();
```
### Pattern 6: Dynamic Port Allocation
```java
Cluster cluster = Cluster.build(3)
.withDynamicPortAllocation(true)
.start();
```
## Configuration
### Available Configuration Parameters
Configuration is applied via `config.set(key, value)` or `config.with(Feature)`:
#### Network Configuration
```java
config.set("broadcast_address", "127.0.0.1")
.set("listen_address", "127.0.0.1")
.set("rpc_address", "127.0.0.1")
.set("storage_port", "7000")
.set("native_transport_port", "9042")
.set("jmx_port", "7199");
```
#### Token Configuration
```java
config.set("num_tokens", "256")
.set("initial_token", "0");
```
#### Memory Configuration
```java
config.set("memtable_heap_space", "512MiB")
.set("key_cache_size", "50MiB")
.set("counter_cache_size", "50MiB")
.set("row_cache_size", "0MiB");
```
#### Compaction & Concurrency
```java
config.set("concurrent_compactors", "4")
.set("concurrent_reads", "32")
.set("concurrent_writes", "32")
.set("concurrent_counter_writes", "32");
```
#### Commitlog Configuration
```java
config.set("commitlog_sync", "periodic")
.set("commitlog_sync_period_in_ms", "10000")
.set("commitlog_segment_size", "32MiB");
```
#### Authentication & Authorization
```java
config.set("authenticator", "PasswordAuthenticator")
.set("authorizer", "CassandraAuthorizer")
.set("role_manager", "CassandraRoleManager");
```
#### Accord Configuration (CEP-15)
```java
config.set("accord.enabled", "true")
.set("accord.journal_directory", "/path/to/journal");
```
#### Data Directories
```java
// Data directories are arrays
config.set("data_file_directories", new String[]{"/path/to/data"})
.set("commitlog_directory", "/path/to/commitlog")
.set("hints_directory", "/path/to/hints")
.set("saved_caches_directory", "/path/to/saved_caches");
```
## Instance Lifecycle Operations
### Starting Instances
```java
// Start with default settings
instance.startup();
// Start with system properties
ClusterUtils.start(instance, properties -> {
properties.set(RING_DELAY, "5000");
properties.set(BOOTSTRAP_SCHEMA_DELAY_MS, "5000");
});
```
### Stopping Instances
```java
// Graceful shutdown (blocking)
ClusterUtils.stopUnchecked(instance);
// Abrupt shutdown (simulates kill -9)
ClusterUtils.stopAbrupt(cluster, instance);
// Stop all instances
ClusterUtils.stopAll(cluster);
// Async shutdown
Future<?> shutdownFuture = instance.shutdown();
shutdownFuture.get(); // wait for completion
```
### Restarting Instances
```java
// Simple restart
ClusterUtils.restartUnchecked(instance);
// Restart with different configuration
instance.shutdown().get();
instance.config().set("concurrent_reads", "64");
instance.startup();
```
### Adding Instances
```java
// Add instance with same config as existing nodes
IInstance newInstance = ClusterUtils.addInstance(cluster);
// Add instance with custom config
IInstance newInstance = ClusterUtils.addInstance(cluster, config -> {
config.set("auto_bootstrap", true);
});
// Add instance to specific DC/rack
IInstance newInstance = ClusterUtils.addInstance(cluster, "dc1", "rack1", config -> {
config.set("num_tokens", "256");
});
// Start the new instance
newInstance.startup();
```
### Replacing Instances
```java
// Replace a failed node
IInstance replacement = ClusterUtils.replaceHostAndStart(cluster, failedInstance);
// Replace with custom properties
IInstance replacement = ClusterUtils.replaceHostAndStart(
cluster,
failedInstance,
(inst, properties) -> {
properties.set(RING_DELAY, "5000");
}
);
```
## Running Code on Instances
### Pattern 1: Run Without Return Value
```java
cluster.get(1).runOnInstance(() -> {
StorageService.instance.forceKeyspaceFlush("system");
});
```
### Pattern 2: Get Return Value (Sync)
```java
String version = cluster.get(1).callOnInstance(() -> {
return FBUtilities.getReleaseVersionString();
});
```
### Pattern 3: Get Return Value (Async)
```java
Future<String> future = cluster.get(1).asyncCallOnInstance(() -> {
return FBUtilities.getReleaseVersionString();
});
String version = future.get();
```
### Pattern 4: Pass Parameters
```java
// Parameters must be serializable
int result = cluster.get(1).callOnInstance(() -> {
// Access Cassandra internals here
return ClusterMetadata.current().directory.allAddresses().size();
});
```
### Pattern 5: Instance-to-Instance Operations
```java
// Get node ID from one instance, use it on another
int nodeId = ClusterUtils.getNodeId(cluster.get(1), cluster.get(2));
NodeId id = new NodeId(nodeId);
cluster.get(2).runOnInstance(() -> {
StorageService.instance.cancelInProgressSequences(id);
});
```
## Query Execution
### Basic Queries
```java
// Execute without results
cluster.coordinator(1).execute(
"INSERT INTO test.users (id, name) VALUES (?, ?)",
ConsistencyLevel.QUORUM,
1, "Alice"
);
// Execute with results
Object[][] rows = cluster.coordinator(1).execute(
"SELECT * FROM test.users WHERE id = ?",
ConsistencyLevel.QUORUM,
1
);
```
### Using Query Results
```java
import org.apache.cassandra.distributed.api.SimpleQueryResult;
SimpleQueryResult result = cluster.coordinator(1).executeWithResult(
"SELECT * FROM test.users",
ConsistencyLevel.QUORUM
);
while (result.hasNext()) {
Row row = result.next();
int id = row.getInteger("id");
String name = row.getString("name");
System.out.println("User: " + id + ", " + name);
}
```
### Pagination
```java
SimpleQueryResult result = cluster.coordinator(1).executeWithPagingWithResult(
"SELECT * FROM test.users",
ConsistencyLevel.QUORUM,
100 // page size
);
```
### Async Execution
```java
Future<SimpleQueryResult> future = cluster.coordinator(1).asyncExecuteWithResult(
"SELECT * FROM test.users",
ConsistencyLevel.QUORUM
);
SimpleQueryResult result = future.get();
```
### Query with Tracing
```java
import java.util.UUID;
UUID sessionId = UUID.randomUUID();
SimpleQueryResult result = cluster.coordinator(1).executeWithTracingWithResult(
sessionId,
"SELECT * FROM test.users",
ConsistencyLevel.QUORUM
);
// Query tracing info from system_traces
cluster.coordinator(1).execute(
"SELECT * FROM system_traces.sessions WHERE session_id = ?",
ConsistencyLevel.ONE,
sessionId
);
```
### Internal Queries (No Coordinator)
```java
// Execute directly on instance internals (bypasses coordinator)
SimpleQueryResult result = instance.executeInternalWithResult(
"SELECT * FROM system.peers WHERE peer=?",
expectedPeer.config().broadcastAddress().getAddress()
);
```
## Message Filtering for Failure Injection
### Pattern 1: Drop Messages Between Nodes
```java
// Drop all mutation requests from node 1 to node 2
cluster.filters()
.inbound()
.from(1).to(2)
.verbs(Verb.MUTATION_REQ.id)
.drop()
.on();
// Verify timeout behavior
assertThatThrownBy(() ->
cluster.coordinator(1).execute(
"INSERT INTO test.users (id, name) VALUES (?, ?)",
ConsistencyLevel.ALL, 1, "Alice"
)
).isInstanceOf(WriteTimeoutException.class);
// Reset filters
cluster.filters().reset();
```
### Pattern 2: Drop All Verbs
```java
cluster.filters()
.allVerbs()
.from(1).to(2)
.drop()
.on();
```
### Pattern 3: Drop Outbound Messages
```java
cluster.filters()
.outbound()
.from(1)
.verbs(Verb.READ_REQ.id)
.drop()
.on();
```
### Pattern 4: Custom Message Matching
```java
cluster.filters()
.inbound()
.from(1).to(2)
.messagesMatching((from, to, message) -> {
// Custom logic to match specific messages
return message.verb() == Verb.MUTATION_REQ.id;
})
.drop()
.on();
```
### Pattern 5: Temporary Filter Control
```java
IMessageFilters.Filter filter = cluster.filters()
.inbound()
.from(1).to(2)
.verbs(Verb.MUTATION_REQ.id)
.drop();
filter.on(); // Enable
// ... perform operations ...
filter.off(); // Disable
```
## ClusterUtils Reference
For comprehensive utilities, see [references/cluster_utils.md](references/cluster_utils.md).
### Most Commonly Used Methods
#### Lifecycle Management
```java
ClusterUtils.stopUnchecked(instance);
ClusterUtils.stopAbrupt(cluster, instance);
ClusterUtils.restartUnchecked(instance);
ClusterUtils.stopAll(cluster);
```
#### Instance Management
```java
ClusterUtils.addInstance(cluster, config -> {...});
ClusterUtils.replaceHostAndStart(cluster, toReplace);
ClusterUtils.start(instance, properties -> {...});
```
#### Ring & Gossip
```java
ClusterUtils.awaitRingJoin(cluster.get(1), newInstance);
ClusterUtils.awaitRingHealthy(cluster.get(1));
ClusterUtils.awaitGossipStatus(instance, target, "NORMAL");
ClusterUtils.assertInRing(instance, expectedInRing);
```
#### CMS & Metadata
```java
ClusterUtils.waitForCMSToQuiesce(cluster);
Epoch epoch = ClusterUtils.getCurrentEpoch(instance);
ClusterUtils.fetchLogFromCMS(instance, awaitedEpoch);
```
#### Directories & Files
```java
List<File> dirs = ClusterUtils.getDataDirectories(instance);
File commitlog = ClusterUtils.getCommitLogDirectory(instance);
File hints = ClusterUtils.getHintsDirectory(instance);
```
## Schema Management
### Creating Keyspaces
```java
cluster.schemaChange("CREATE KEYSPACE test WITH replication = " +
"{'class': 'SimpleStrategy', 'replication_factor': 2}");
// Multi-DC keyspace
cluster.schemaChange("CREATE KEYSPACE test WITH replication = " +
"{'class': 'NetworkTopologyStrategy', " +
"'dc1': 2, 'dc2': 2}");
```
### Creating Tables
```java
cluster.schemaChange("CREATE TABLE test.users (" +
"id int PRIMARY KEY, " +
"name text, " +
"email text)");
// With clustering
cluster.schemaChange("CREATE TABLE test.events (" +
"user_id int, " +
"event_time timestamp, " +
"event_type text, " +
"PRIMARY KEY (user_id, event_time)) " +
"WITH CLUSTERING ORDER BY (event_time DESC)");
```
### Waiting for Schema Agreement
```java
// Manual wait
try (Cluster.SchemaChangeMonitor monitor = cluster.new SchemaChangeMonitor()) {
monitor.startPolling();
cluster.schemaChange("CREATE TABLE ...");
monitor.waitForCompletion();
}
// Using ClusterUtils
ClusterUtils.awaitGossipSchemaMatch(cluster);
```
## Monitoring & Observables
### Log Watching
```java
// Mark position and wait for log message
long mark = instance.logs().mark();
// ... perform operation ...
instance.logs().watchFor(mark, "Bootstrap completed");
// Grep logs
List<String> errors = instance.logs().grep("ERROR").getResult();
```
### Nodetool Integration
```java
import org.apache.cassandra.distributed.api.NodeToolResult;
NodeToolResult result = instance.nodetoolResult("status");
result.asserts().success();
String output = result.getStdout();
// Common nodetool commands
instance.nodetool("flush");
instance.nodetool("repair");
instance.nodetool("compact");
instance.nodetool("decommission");
```
### Ring Information
```java
import org.apache.cassandra.distributed.shared.ClusterUtils.RingInstanceDetails;
List<RingInstanceDetails> ring = ClusterUtils.ring(instance);
for (RingInstanceDetails details : ring) {
System.out.println("Address: " + details.getAddress());
System.out.println("Status: " + details.getStatus()); // Up/Down
System.out.println("State: " + details.getState()); // Normal/Leaving/Joining
System.out.println("Token: " + details.getToken());
}
```
### Gossip Information
```java
Map<String, Map<String, String>> gossip = ClusterUtils.gossipInfo(instance);
for (Map.Entry<String, Map<String, String>> entry : gossip.entrySet()) {
String address = entry.getKey();
Map<String, String> state = entry.getValue();
System.out.println(address + " -> " + state.get("STATUS"));
}
```
## Classloader Issues & Debugging
### Understanding Classloader Isolation
The in-JVM dtest framework uses multiple classloaders for isolation:
- **Test ClassLoader**: Where your test code runs
- **Shared ClassLoader**: Primitives, APIs, config objects
- **Instance ClassLoaders**: Per-instance Cassandra code (isolated)
### Common Classloader Issues
#### Issue 1: Same Class, Different ClassLoaders
**Symptom**: `ClassCastException` even though classes appear identical
```java
// WRONG - This will fail
cluster.get(1).runOnInstance(() -> {
Token token = new Murmur3Partitioner.LongToken(0); // Loaded in test classloader
// Try to use token with instance internals -> ClassCastException
});
// CORRECT - Create objects inside runOnInstance
cluster.get(1).runOnInstance(() -> {
Token token = new Murmur3Partitioner.LongToken(0); // Loaded in instance classloader
// Now token is in correct classloader
});
```
#### Issue 2: Serialization Failures
**Symptom**: `NotSerializableException` or `ClassNotFoundException`
```java
// WRONG - Non-serializable lambda capture
String localVar = "test";
cluster.get(1).runOnInstance(() -> {
System.out.println(localVar); // May fail if localVar isn't Serializable
});
// CORRECT - Only pass serializable primitives
final String localVar = "test"; // String is Serializable
cluster.get(1).runOnInstance(() -> {
System.out.println(localVar);
});
```
**Lambdas are serialized** and transferred across classloaders. Only capture:
- Primitives (int, long, String, etc.)
- Serializable objects marked with `@Shared`
- Nothing else from test classloader
#### Issue 3: Static State Not Shared
**Symptom**: Static fields don't reflect changes across instances
```java
// Each instance has its own DatabaseDescriptor, StorageService, etc.
// Static state is NOT shared between instances
// To coordinate across instances, use:
// 1. CQL queries to share data
// 2. Message passing
// 3. Instance-specific operations
```
### Debugging Techniques
#### Technique 1: Print Classloader Information
```java
cluster.get(1).runOnInstance(() -> {
System.out.println("ClassLoader: " +
Thread.currentThread().getContextClassLoader());
System.out.println("Token class loader: " +
Token.class.getClassLoader());
});
```
#### Technique 2: Transfer Objects Explicitly
```java
import org.apache.cassandra.distributed.impl.IsolatedExecutor;
// Transfer object to instance classloader
Serializable transferred = instance.transfer(mySerializableObject);
```
#### Technique 3: Use @Shared and @Isolated Annotations
```java
// In your test code
import org.apache.cassandra.utils.Shared;
import org.apache.cassandra.utils.Isolated;
@Shared // Loaded in shared classloader
public class MySharedClass implements Serializable {
// Can be shared across boundaries
}
@Isolated // Each instance gets own copy
public class MyIsolatedClass {
// Not shared
}
```
#### Technique 4: Check Serialization
```java
// Verify object can be serialized
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(myObject);
oos.close();
System.out.println("Object is serializable");
} catch (NotSerializableException e) {
System.out.println("Object is NOT serializable: " + e.getMessage());
}
```
### Best Practices for Classloader Safety
1. **Keep lambdas simple**: Minimize captured variables
2. **Use primitives**: Pass int, String, long instead of complex objects
3. **Create objects inside runOnInstance**: Don't create in test, pass to instance
4. **Avoid static state**: Don't rely on static fields across instances
5. **Use coordinator for data sharing**: Query data rather than passing objects
## Advanced Patterns
### Pattern: Pausing & Resuming CMS Operations
```java
// Pause before committing at specific epoch
Callable<Epoch> pauseHandle = ClusterUtils.pauseBeforeCommit(
cmsInstance,
transformation -> transformation instanceof AddNode
);
// Wait for pause
Epoch epoch = pauseHandle.call();
// Do something while paused
// ...
// Resume
ClusterUtils.unpauseCommits(cmsInstance);
```
### Pattern: Pausing Enactment
```java
// Pause before enacting specific epoch
Callable<Void> pauseHandle = ClusterUtils.pauseBeforeEnacting(instance, epoch);
// Wait for pause
pauseHandle.call();
// Do something while paused
// ...
// Resume
ClusterUtils.unpauseEnactment(instance);
```
### Pattern: Testing with Log Monitoring
```java
ClusterUtils.runAndWaitForLogs(
() -> {
cluster.coordinator(1).execute("INSERT INTO test.users VALUES (1, 'Alice')",
ConsistencyLevel.QUORUM);
},
"Mutation applied",
cluster.get(1), cluster.get(2), cluster.get(3)
);
```
### Pattern: Custom Instance Initialization
```java
Cluster cluster = Cluster.build()
.withNodes(3)
.withInstanceInitializer((cl, i) -> {
// Custom initialization logic
System.out.println("Initializing instance " + i);
})
.start();
```
### Pattern: Testing Node Replacement
```java
// Original node
IInvokableInstance original = cluster.get(2);
// Stop original
ClusterUtils.stopAbrupt(cluster, original);
// Replace with new node
IInvokableInstance replacement = ClusterUtils.replaceHostAndStart(
cluster,
original,
(inst, properties) -> {
properties.set(RING_DELAY, "5000");
}
);
// Verify replacement joined
ClusterUtils.awaitRingJoin(cluster.get(1), replacement);
```
### Pattern: Testing Decommission
```java
IInvokableInstance leaving = cluster.get(3);
boolean success = ClusterUtils.decommission(leaving);
Assert.assertTrue("Decommission failed", success);
// Verify node left
ClusterUtils.assertNotInRing(cluster.get(1), leaving);
```
### Pattern: Multi-DC Token Ranges
```java
List<ClusterUtils.Range> ranges = ClusterUtils.getPrimaryRanges(
cluster.get(1),
"test" // keyspace
);
for (ClusterUtils.Range range : ranges) {
System.out.println("Range: " + range.left() + " to " + range.right());
}
```
## Complete Test Examples
### Example 1: Basic Read/Write Test
```java
public class BasicReadWriteTest extends TestBaseImpl {
@Test
public void testReadWrite() throws IOException {
try (Cluster cluster = Cluster.build(3).start()) {
cluster.schemaChange("CREATE KEYSPACE test WITH replication = " +
"{'class': 'SimpleStrategy', 'replication_factor': 2}");
cluster.schemaChange("CREATE TABLE test.users (id int PRIMARY KEY, name text)");
// Write
cluster.coordinator(1).execute(
"INSERT INTO test.users (id, name) VALUES (?, ?)",
ConsistencyLevel.QUORUM, 1, "Alice");
// Read
Object[][] rows = cluster.coordinator(2).execute(
"SELECT * FROM test.users WHERE id = ?",
ConsistencyLevel.QUORUM, 1);
Assert.assertEquals(1, rows.length);
Assert.assertEquals(1, rows[0][0]);
Assert.assertEquals("Alice", rows[0][1]);
}
}
}
```
### Example 2: Testing Write Timeout with Message Filtering
```java
public class WriteTimeoutTest extends TestBaseImpl {
@Test
public void testWriteTimeout() throws IOException {
try (Cluster cluster = Cluster.build(3).start()) {
cluster.schemaChange("CREATE KEYSPACE test WITH replication = " +
"{'class': 'SimpleStrategy', 'replication_factor': 3}");
cluster.schemaChange("CREATE TABLE test.data (k int PRIMARY KEY, v int)");
// Drop mutations to node 2 and 3
cluster.filters().inbound()
.to(2, 3)
.verbs(Verb.MUTATION_REQ.id)
.drop()
.on();
// This should timeout (can't reach QUORUM)
try {
cluster.coordinator(1).execute(
"INSERT INTO test.data (k, v) VALUES (?, ?)",
ConsistencyLevel.QUORUM, 1, 100);
Assert.fail("Expected WriteTimeoutException");
} catch (RuntimeException e) {
Assert.assertTrue(e.getCause() instanceof WriteTimeoutException);
}
cluster.filters().reset();
}
}
}
```
### Example 3: Testing Node Addition
```java
public class NodeAdditionTest extends TestBaseImpl {
@Test
public void testAddNode() throws IOException {
try (Cluster cluster = Cluster.build(2).start()) {
cluster.schemaChange("CREATE KEYSPACE test WITH replication = " +
"{'class': 'SimpleStrategy', 'replication_factor': 2}");
// Add and start new node
IInvokableInstance newNode = ClusterUtils.addInstance(cluster, config -> {
config.set("auto_bootstrap", true);
});
newNode.startup();
// Wait for node to join
ClusterUtils.awaitRingJoin(cluster.get(1), newNode);
// Verify all nodes see the new node
ClusterUtils.assertInRing(cluster.get(1), newNode);
ClusterUtils.assertInRing(cluster.get(2), newNode);
}
}
}
```
### Example 4: Running Code on Instance
```java
public class InstanceCodeTest extends TestBaseImpl {
@Test
public void testRunOnInstance() throws IOException {
try (Cluster cluster = Cluster.build(2).start()) {
cluster.schemaChange("CREATE KEYSPACE test WITH replication = " +
"{'class': 'SimpleStrategy', 'replication_factor': 1}");
cluster.schemaChange("CREATE TABLE test.data (k int PRIMARY KEY)");
// Insert data
cluster.coordinator(1).execute("INSERT INTO test.data (k) VALUES (1)",
ConsistencyLevel.ONE);
// Force flush on node 1
cluster.get(1).runOnInstance(() -> {
StorageService.instance.forceKeyspaceFlush("test");
});
// Verify flush happened by checking for sstables
int sstableCount = cluster.get(1).callOnInstance(() -> {
ColumnFamilyStore cfs = Keyspace.open("test")
.getColumnFamilyStore("data");
return cfs.getLiveSSTables().size();
});
Assert.assertTrue("Expected at least one sstable", sstableCount > 0);
}
}
}
```
### Example 5: Multi-DC Test
```java
public class MultiDCTest extends TestBaseImpl {
@Test
public void testMultiDC() throws IOException {
try (Cluster cluster = builder()
.withRacks(2, 1, 2) // 2 DCs, 1 rack each, 2 nodes per rack
.start()) {
cluster.schemaChange("CREATE KEYSPACE test WITH replication = " +
"{'class': 'NetworkTopologyStrategy', " +
"'datacenter1': 2, 'datacenter2': 2}");
cluster.schemaChange("CREATE TABLE test.data (k int PRIMARY KEY)");
// Write from DC1
cluster.coordinator(1).execute("INSERT INTO test.data (k) VALUES (1)",
ConsistencyLevel.LOCAL_QUORUM);
// Read from DC2
Object[][] rows = cluster.coordinator(3).execute(
"SELECT * FROM test.data WHERE k = 1",
ConsistencyLevel.LOCAL_QUORUM);
Assert.assertEquals(1, rows.length);
}
}
}
```
## Troubleshooting Guide
### Problem: Tests are flaky or hang
**Possible causes:**
1. Schema not fully agreed before operations
2. Insufficient timeout for async operations
3. Message filters not properly reset
**Solutions:**
```java
// Wait for schema agreement
ClusterUtils.awaitGossipSchemaMatch(cluster);
// Use longer timeouts for operations
future.get(30, TimeUnit.SECONDS);
// Always reset filters in finally block
try {
cluster.filters().inbound().drop().on();
// test code
} finally {
cluster.filters().reset();
}
```
### Problem: ClassCastException with tokens or types
**Cause:** Object created in wrong classloader
**Solution:**
```java
// Create objects inside runOnInstance
cluster.get(1).runOnInstance(() -> {
Token token = new Murmur3Partitioner.LongToken(0);
// Use token here
});
```
### Problem: NoClassDefFoundError or ClassNotFoundException
**Cause:** Class not available in instance classloader
**Solution:** Ensure class is in Cassandra dependencies or marked `@Shared`
### Problem: Test times out waiting for ring/gossip
**Cause:** Network features not enabled
**Solution:**
```java
Cluster cluster = builder()
.withConfig(c -> c.with(GOSSIP, NETWORK))
.start();
```
### Problem: Port conflicts
**Cause:** Static port allocation
**Solution:**
```java
Cluster cluster = Cluster.build(3)
.withDynamicPortAllocation(true)
.start();
```
## Additional Resources
- **ClusterUtils**: See [references/cluster_utils.md](references/cluster_utils.md) for exhaustive utility method documentation
- **Advanced Patterns**: See [references/advanced_patterns.md](references/advanced_patterns.md) for CMS operations, Accord testing, and complex scenarios
- **Classloader Deep Dive**: See [references/classloader_guide.md](references/classloader_guide.md) for detailed classloader mechanics and debugging
More agent context in apache/cassandra
11 other files this repository gives its agents.
AGENTS.md
CLAUDE.md
Skill
- bug-archaeology.claude/skills/bug-archaeology/SKILL.md
- deep-review.claude/skills/deep-review/SKILL.md
- heatmap.claude/skills/heatmap/SKILL.md
- mega-review.claude/skills/mega-review/SKILL.md
- patch-explainer.claude/skills/patch-explainer/SKILL.md
- shallow-review.claude/skills/shallow-review/SKILL.md
- targeted-review.claude/skills/targeted-review/SKILL.md
- tla-plus.claude/skills/tla-plus/SKILL.md
- write-reproducer.claude/skills/write-reproducer/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
Reports can't be read right now.
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

