diff --git a/CHANGES.txt b/CHANGES.txt index 143dc3864b1d..5f9a1617c582 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.0.28 + * Fix restarting of services on gossipping-only member (CASSANDRA-17752) * Fix writetime and ttl functions forbidden for collections instead of multicell columns (CASSANDRA-17628) * Supress CVE-2020-7238 (CASSANDRA-17697) * Fix issue where frozen maps may not be serialized in the correct order (CASSANDRA-17623) diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index c78bb0803c57..180a4ef6d0d1 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -346,7 +346,7 @@ public void stopGossiping() { if (initialized) { - if (!isNormal()) + if (!isNormal() && joinRing) throw new IllegalStateException("Unable to stop gossip because the node is not in the normal state. Try to stop the node instead."); logger.warn("Stopping gossip by operator request"); @@ -4489,7 +4489,7 @@ synchronized void checkServiceAllowedToStart(String service) if (isShutdown()) // do not rely on operationMode in case it gets changed to decomissioned or other throw new IllegalStateException(String.format("Unable to start %s because the node was drained.", service)); - if (!isNormal()) + if (!isNormal() && joinRing) // if the node is not joining the ring, it is gossipping-only member which is in STARTING state forever throw new IllegalStateException(String.format("Unable to start %s because the node is not in the normal state.", service)); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/GossipTest.java b/test/distributed/org/apache/cassandra/distributed/test/GossipTest.java index 0cff2b07aa28..25bb97332681 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/GossipTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/GossipTest.java @@ -50,9 +50,14 @@ import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.cassandra.distributed.action.GossipHelper.withProperty; import static org.apache.cassandra.distributed.api.Feature.GOSSIP; import static org.apache.cassandra.distributed.api.Feature.NETWORK; +import static org.apache.cassandra.distributed.api.TokenSupplier.evenlyDistributedTokens; +import static org.apache.cassandra.distributed.shared.NetworkTopology.singleDcNetworkTopology; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class GossipTest extends TestBaseImpl { @@ -266,6 +271,37 @@ public void gossipShutdownUpdatesTokenMetadata() throws Exception } } + @Test + public void restartGossipOnGossippingOnlyMember() throws Throwable + { + int originalNodeCount = 1; + int expandedNodeCount = originalNodeCount + 1; + + try (Cluster cluster = builder().withNodes(originalNodeCount) + .withTokenSupplier(evenlyDistributedTokens(expandedNodeCount, 1)) + .withNodeIdTopology(singleDcNetworkTopology(expandedNodeCount, "dc0", "rack0")) + .withConfig(config -> config.with(NETWORK, GOSSIP)) + .start()) + { + IInstanceConfig config = cluster.newInstanceConfig(); + IInvokableInstance gossippingOnlyMember = cluster.bootstrap(config); + withProperty("cassandra.join_ring", Boolean.toString(false), () -> gossippingOnlyMember.startup(cluster)); + + assertTrue(gossippingOnlyMember.callOnInstance((IIsolatedExecutor.SerializableCallable) + () -> StorageService.instance.isGossipRunning())); + + gossippingOnlyMember.runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> StorageService.instance.stopGossiping()); + + assertFalse(gossippingOnlyMember.callOnInstance((IIsolatedExecutor.SerializableCallable) + () -> StorageService.instance.isGossipRunning())); + + gossippingOnlyMember.runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> StorageService.instance.startGossiping()); + + assertTrue(gossippingOnlyMember.callOnInstance((IIsolatedExecutor.SerializableCallable) + () -> StorageService.instance.isGossipRunning())); + } + } + void assertPendingRangesForPeer(final boolean expectPending, final InetAddress movingAddress, final Cluster cluster) { for (IInvokableInstance inst : new IInvokableInstance[]{ cluster.get(1), cluster.get(3)}) diff --git a/test/distributed/org/apache/cassandra/distributed/test/NativeProtocolTest.java b/test/distributed/org/apache/cassandra/distributed/test/NativeProtocolTest.java index 92751efe817e..a8bf5ebf2044 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/NativeProtocolTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/NativeProtocolTest.java @@ -26,14 +26,24 @@ import com.datastax.driver.core.Session; import com.datastax.driver.core.SimpleStatement; import com.datastax.driver.core.Statement; +import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ICluster; +import org.apache.cassandra.distributed.api.IInstanceConfig; +import org.apache.cassandra.distributed.api.IInvokableInstance; +import org.apache.cassandra.distributed.api.IIsolatedExecutor; import org.apache.cassandra.distributed.impl.RowUtil; +import org.apache.cassandra.service.StorageService; +import static org.apache.cassandra.distributed.action.GossipHelper.withProperty; 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.apache.cassandra.distributed.api.TokenSupplier.evenlyDistributedTokens; import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows; import static org.apache.cassandra.distributed.shared.AssertUtils.row; +import static org.apache.cassandra.distributed.shared.NetworkTopology.singleDcNetworkTopology; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; // TODO: this test should be removed after running in-jvm dtests is set up via the shared API repository public class NativeProtocolTest extends TestBaseImpl @@ -78,4 +88,35 @@ public void withCounters() throws Throwable cluster.close(); } } + + @Test + public void restartTransportOnGossippingOnlyMember() throws Throwable + { + int originalNodeCount = 1; + int expandedNodeCount = originalNodeCount + 1; + + try (Cluster cluster = builder().withNodes(originalNodeCount) + .withTokenSupplier(evenlyDistributedTokens(expandedNodeCount, 1)) + .withNodeIdTopology(singleDcNetworkTopology(expandedNodeCount, "dc0", "rack0")) + .withConfig(config -> config.with(NETWORK, GOSSIP, NATIVE_PROTOCOL)) + .start()) + { + IInstanceConfig config = cluster.newInstanceConfig(); + IInvokableInstance gossippingOnlyMember = cluster.bootstrap(config); + withProperty("cassandra.join_ring", Boolean.toString(false), () -> gossippingOnlyMember.startup(cluster)); + + assertTrue(gossippingOnlyMember.callOnInstance((IIsolatedExecutor.SerializableCallable) + () -> StorageService.instance.isNativeTransportRunning())); + + gossippingOnlyMember.runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> StorageService.instance.stopNativeTransport()); + + assertFalse(gossippingOnlyMember.callOnInstance((IIsolatedExecutor.SerializableCallable) + () -> StorageService.instance.isNativeTransportRunning())); + + gossippingOnlyMember.runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> StorageService.instance.startNativeTransport()); + + assertTrue(gossippingOnlyMember.callOnInstance((IIsolatedExecutor.SerializableCallable) + () -> StorageService.instance.isNativeTransportRunning())); + } + } } \ No newline at end of file diff --git a/test/distributed/org/apache/cassandra/distributed/test/ThriftClientTest.java b/test/distributed/org/apache/cassandra/distributed/test/ThriftTest.java similarity index 52% rename from test/distributed/org/apache/cassandra/distributed/test/ThriftClientTest.java rename to test/distributed/org/apache/cassandra/distributed/test/ThriftTest.java index 0548e4775a00..cde1bdd81671 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/ThriftClientTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/ThriftTest.java @@ -28,16 +28,29 @@ import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.Feature; +import org.apache.cassandra.distributed.api.IInstanceConfig; +import org.apache.cassandra.distributed.api.IInvokableInstance; +import org.apache.cassandra.distributed.api.IIsolatedExecutor; import org.apache.cassandra.distributed.api.QueryResults; import org.apache.cassandra.distributed.api.SimpleQueryResult; import org.apache.cassandra.distributed.shared.AssertUtils; +import org.apache.cassandra.service.StorageService; import org.apache.cassandra.thrift.Column; import org.apache.cassandra.thrift.ColumnOrSuperColumn; import org.apache.cassandra.thrift.Mutation; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.thrift.TException; -public class ThriftClientTest extends TestBaseImpl +import static org.apache.cassandra.distributed.action.GossipHelper.withProperty; +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.apache.cassandra.distributed.api.TokenSupplier.evenlyDistributedTokens; +import static org.apache.cassandra.distributed.shared.NetworkTopology.singleDcNetworkTopology; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class ThriftTest extends TestBaseImpl { @Test public void writeThenReadCQL() throws IOException, TException @@ -66,4 +79,35 @@ public void writeThenReadCQL() throws IOException, TException AssertUtils.assertRows(qr, QueryResults.builder().row(0, 0).build()); } } + + @Test + public void restartThriftOnGossippingOnlyMember() throws Throwable + { + int originalNodeCount = 1; + int expandedNodeCount = originalNodeCount + 1; + + try (Cluster cluster = builder().withNodes(originalNodeCount) + .withTokenSupplier(evenlyDistributedTokens(expandedNodeCount, 1)) + .withNodeIdTopology(singleDcNetworkTopology(expandedNodeCount, "dc0", "rack0")) + .withConfig(config -> config.with(NETWORK, GOSSIP, NATIVE_PROTOCOL)) + .start()) + { + IInstanceConfig config = cluster.newInstanceConfig(); + IInvokableInstance gossippingOnlyMember = cluster.bootstrap(config); + withProperty("cassandra.join_ring", Boolean.toString(false), () -> gossippingOnlyMember.startup(cluster)); + + assertTrue(gossippingOnlyMember.callOnInstance((IIsolatedExecutor.SerializableCallable) + () -> StorageService.instance.isRPCServerRunning())); + + gossippingOnlyMember.runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> StorageService.instance.stopRPCServer()); + + assertFalse(gossippingOnlyMember.callOnInstance((IIsolatedExecutor.SerializableCallable) + () -> StorageService.instance.isRPCServerRunning())); + + gossippingOnlyMember.runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> StorageService.instance.startRPCServer()); + + assertTrue(gossippingOnlyMember.callOnInstance((IIsolatedExecutor.SerializableCallable) + () -> StorageService.instance.isRPCServerRunning())); + } + } }