This is an automated email from the ASF dual-hosted git repository.

bdeggleston pushed a commit to branch cassandra-3.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/cassandra-3.0 by this push:
     new fee7a10  Prevent invoking enable/disable gossip when not in NORMAL
fee7a10 is described below

commit fee7a10823da1e29bd0e6504fea9679389180c9e
Author: yifan-c <yc25c...@gmail.com>
AuthorDate: Mon Sep 28 13:11:05 2020 -0700

    Prevent invoking enable/disable gossip when not in NORMAL
    
    Patch by Yifan Cai; Reviewed by Brandon Williams and Blake Eggleston for 
CASSANDRA-16146
---
 CHANGES.txt                                        |   1 +
 .../apache/cassandra/service/StorageService.java   |  18 +++-
 .../cassandra/distributed/test/GossipTest.java     | 114 ++++++++++++++++++++-
 3 files changed, 131 insertions(+), 2 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 0f016d0..bfc0a81 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 3.0.23:
+ * Prevent invoking enable/disable gossip when not in NORMAL (CASSANDRA-16146)
  * Fix OOM when terminating repair session (CASSANDRA-15902)
  * Avoid marking shutting down nodes as up after receiving gossip shutdown 
message (CASSANDRA-16094)
  * Check SSTables for latest version before dropping compact storage 
(CASSANDRA-16063)
diff --git a/src/java/org/apache/cassandra/service/StorageService.java 
b/src/java/org/apache/cassandra/service/StorageService.java
index 1e544b2..7645091 100644
--- a/src/java/org/apache/cassandra/service/StorageService.java
+++ b/src/java/org/apache/cassandra/service/StorageService.java
@@ -323,6 +323,9 @@ public class StorageService extends 
NotificationBroadcasterSupport implements IE
     {
         if (initialized)
         {
+            if (!isNormal())
+                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");
 
             if (isNativeTransportRunning())
@@ -1221,6 +1224,11 @@ public class StorageService extends 
NotificationBroadcasterSupport implements IE
         DatabaseDescriptor.setIncrementalBackupsEnabled(value);
     }
 
+    @VisibleForTesting // only used by test
+    public void setMovingModeUnsafe() {
+        setMode(Mode.MOVING, true);
+    }
+
     private void setMode(Mode m, boolean log)
     {
         setMode(m, null, log);
@@ -3690,7 +3698,7 @@ public class StorageService extends 
NotificationBroadcasterSupport implements IE
             throw new UnsupportedOperationException("local node is not a 
member of the token ring yet");
         if (tokenMetadata.cloneAfterAllLeft().sortedTokens().size() < 2)
             throw new UnsupportedOperationException("no other normal nodes in 
the ring; decommission would be pointless");
-        if (operationMode != Mode.NORMAL)
+        if (!isNormal())
             throw new UnsupportedOperationException("Node in " + operationMode 
+ " state; wait for status to become normal or restart");
 
         PendingRangeCalculatorService.instance.blockUntilFinished();
@@ -4209,6 +4217,11 @@ public class StorageService extends 
NotificationBroadcasterSupport implements IE
         return operationMode == Mode.DRAINING;
     }
 
+    public boolean isNormal()
+    {
+        return operationMode == Mode.NORMAL;
+    }
+
     public String getDrainProgress()
     {
         return String.format("Drained %s/%s ColumnFamilies", remainingCFs, 
totalCFs);
@@ -4371,6 +4384,9 @@ public class StorageService extends 
NotificationBroadcasterSupport implements IE
 
         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())
+            throw new IllegalStateException(String.format("Unable to start %s 
because the node is not in the normal state.", service));
     }
 
     // Never ever do this at home. Used by tests.
diff --git 
a/test/distributed/org/apache/cassandra/distributed/test/GossipTest.java 
b/test/distributed/org/apache/cassandra/distributed/test/GossipTest.java
index 83c62c8..32ecb95 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/GossipTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/GossipTest.java
@@ -18,16 +18,25 @@
 
 package org.apache.cassandra.distributed.test;
 
+import java.io.Closeable;
 import java.net.InetAddress;
 import java.util.Collection;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.LockSupport;
 import java.util.stream.Collectors;
 
 import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.Assert;
 import org.junit.Test;
 
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
 import org.apache.cassandra.dht.Token;
 import org.apache.cassandra.distributed.Cluster;
 import org.apache.cassandra.gms.ApplicationState;
@@ -36,6 +45,8 @@ import org.apache.cassandra.gms.Gossiper;
 import org.apache.cassandra.service.StorageService;
 import org.apache.cassandra.utils.FBUtilities;
 
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
 import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
 import static org.apache.cassandra.distributed.api.Feature.NETWORK;
 
@@ -111,5 +122,106 @@ public class GossipTest extends TestBaseImpl
             Assert.assertEquals(expectTokens, tokens);
         }
     }
-    
+
+    public static class BBBootstrapInterceptor
+    {
+        final static CountDownLatch bootstrapReady = new CountDownLatch(1);
+        final static CountDownLatch bootstrapStart = new CountDownLatch(1);
+        static void install(ClassLoader cl, int nodeNumber)
+        {
+            if (nodeNumber != 2)
+                return;
+            new ByteBuddy().rebase(StorageService.class)
+                           .method(named("bootstrap").and(takesArguments(1)))
+                           
.intercept(MethodDelegation.to(BBBootstrapInterceptor.class))
+                           .make()
+                           .load(cl, ClassLoadingStrategy.Default.INJECTION);
+        }
+
+        public static boolean bootstrap(Collection<Token> tokens) throws 
Exception
+        {
+            bootstrapStart.countDown();
+            Uninterruptibles.awaitUninterruptibly(bootstrapReady);
+            return false; // bootstrap fails
+        }
+    }
+
+    @Test
+    public void testPreventStoppingGossipDuringBootstrap() throws Exception
+    {
+        ExecutorService es = Executors.newFixedThreadPool(1);
+        try (Cluster cluster = builder().withNodes(2)
+                                        .withConfig(config -> 
config.with(GOSSIP)
+                                                                    
.with(NETWORK)
+                                                                    
.set("auto_bootstrap", true))
+                                        
.withInstanceInitializer(BBBootstrapInterceptor::install)
+                                        .createWithoutStarting();
+             Closeable ignored = es::shutdown)
+        {
+            Runnable test = () ->
+            {
+                try
+                {
+                    cluster.get(2).runOnInstance(() -> {
+                        
Uninterruptibles.awaitUninterruptibly(BBBootstrapInterceptor.bootstrapStart);
+                        BBBootstrapInterceptor.bootstrapReady.countDown();
+                        try
+                        {
+                            StorageService.instance.stopGossiping();
+
+                            Assert.fail("stopGossiping did not fail!");
+                        }
+                        catch (Exception ex)
+                        {
+                            Assert.assertSame(ex.getClass(), 
IllegalStateException.class);
+                            Assert.assertEquals(ex.getMessage(), "Unable to 
stop gossip because the node is not in the normal state. Try to stop the node 
instead.");
+                        }
+                    });
+                }
+                finally
+                {
+                    // shut down the node2 to speed up cluster startup.
+                    cluster.get(2).shutdown();
+                }
+            };
+            Future<?> testResult = es.submit(test);
+            try
+            {
+                cluster.startup();
+            }
+            catch (Exception ex) {
+                // ignore exceptions from startup process. More interested in 
the test result.
+            }
+            testResult.get();
+        }
+
+        es.awaitTermination(5, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testPreventEnablingGossipDuringMove() throws Exception
+    {
+        try (Cluster cluster = builder().withNodes(2)
+                                        .withConfig(config -> 
config.with(GOSSIP)
+                                                                    
.with(NETWORK))
+                                        .start())
+        {
+            cluster.get(1).runOnInstance(() -> {
+                StorageService.instance.stopGossiping();
+                StorageService.instance.setMovingModeUnsafe();
+                try
+                {
+                    StorageService.instance.startGossiping();
+
+                    Assert.fail("startGossiping did not fail!");
+                }
+                catch (Exception ex)
+                {
+                    Assert.assertSame(ex.getClass(), 
IllegalStateException.class);
+                    Assert.assertEquals(ex.getMessage(), "Unable to start 
gossip because the node is not in the normal state.");
+                }
+            });
+        }
+    }
+
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to