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

marcuse pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new ebbdd8bb81 Make it possible to abort all kinds of multi step operations
ebbdd8bb81 is described below

commit ebbdd8bb81cff1f120ae86a59a3934f26c30b6b7
Author: Marcus Eriksson <marc...@apache.org>
AuthorDate: Wed Jan 15 14:15:47 2025 +0100

    Make it possible to abort all kinds of multi step operations
    
    Patch by marcuse; reviewed by Sam Tunnicliffe for CASSANDRA-20217
---
 CHANGES.txt                                        |  1 +
 .../apache/cassandra/service/StorageService.java   | 14 +++++-
 .../cassandra/service/StorageServiceMBean.java     |  5 +-
 .../tcm/sequences/SingleNodeSequences.java         | 57 +++++++++++++++++-----
 src/java/org/apache/cassandra/tools/NodeProbe.java | 14 +++++-
 src/java/org/apache/cassandra/tools/NodeTool.java  |  3 ++
 .../cassandra/tools/nodetool/Decommission.java     | 14 +++++-
 .../org/apache/cassandra/tools/nodetool/Move.java  | 24 ++++-----
 .../cassandra/tools/nodetool/RemoveNode.java       | 20 +++++---
 .../distributed/test/FailingMoveTest.java          | 44 ++++++++++++++++-
 .../cassandra/distributed/test/RemoveNodeTest.java |  2 +-
 .../distributed/test/ring/DecommissionTest.java    | 56 +++++++++++++++++++++
 12 files changed, 216 insertions(+), 38 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index c0b984e5ca..4202555d11 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 5.1
+ * Make it possible to abort all kinds of multi step operations 
(CASSANDRA-20217)
  * Do not leak non-Java exceptions when calling snapshot operations via JMX 
(CASSANDRA-20335)
  * Implement NOT_NULL constraint (CASSANDRA-20276)
  * Improve error messages for constraints (CASSANDRA-20266)
diff --git a/src/java/org/apache/cassandra/service/StorageService.java 
b/src/java/org/apache/cassandra/service/StorageService.java
index 5e16bae944..252f451d85 100644
--- a/src/java/org/apache/cassandra/service/StorageService.java
+++ b/src/java/org/apache/cassandra/service/StorageService.java
@@ -3381,6 +3381,11 @@ public class StorageService extends 
NotificationBroadcasterSupport implements IE
         SingleNodeSequences.decommission(true, force);
     }
 
+    public void abortDecommission(String nodeId)
+    {
+        SingleNodeSequences.abortDecommission(nodeId);
+    }
+
     public void shutdownNetworking()
     {
         shutdownClientServers();
@@ -3461,9 +3466,9 @@ public class StorageService extends 
NotificationBroadcasterSupport implements IE
     }
 
     @Override
-    public void abortMove()
+    public void abortMove(String nodeId)
     {
-        SingleNodeSequences.abortMove();
+        SingleNodeSequences.abortMove(nodeId);
     }
 
     public String getRemovalStatus()
@@ -3533,6 +3538,11 @@ public class StorageService extends 
NotificationBroadcasterSupport implements IE
         SingleNodeSequences.removeNode(toRemove, force);
     }
 
+    public void abortRemoveNode(String nodeId)
+    {
+        SingleNodeSequences.abortRemoveNode(nodeId);
+    }
+
     public void assassinateEndpoint(String address)
     {
         try
diff --git a/src/java/org/apache/cassandra/service/StorageServiceMBean.java 
b/src/java/org/apache/cassandra/service/StorageServiceMBean.java
index f2b75195c1..57dfcea673 100644
--- a/src/java/org/apache/cassandra/service/StorageServiceMBean.java
+++ b/src/java/org/apache/cassandra/service/StorageServiceMBean.java
@@ -523,7 +523,7 @@ public interface StorageServiceMBean extends 
NotificationEmitter
      * @param force Decommission even if this will reduce N to be less than RF.
      */
     public void decommission(boolean force) throws InterruptedException;
-
+    public void abortDecommission(String nodeId);
     /**
      * Returns whether a node has failed to decommission.
      *
@@ -547,7 +547,7 @@ public interface StorageServiceMBean extends 
NotificationEmitter
      */
     public void move(String newToken) throws IOException;
     public void resumeMove();
-    public void abortMove();
+    public void abortMove(String nodeId);
 
     /**
      * removeToken removes token (and all data associated with
@@ -555,6 +555,7 @@ public interface StorageServiceMBean extends 
NotificationEmitter
      */
     public void removeNode(String token);
     public void removeNode(String token, boolean force);
+    public void abortRemoveNode(String nodeId);
 
     public void assassinateEndpoint(String addr);
 
diff --git 
a/src/java/org/apache/cassandra/tcm/sequences/SingleNodeSequences.java 
b/src/java/org/apache/cassandra/tcm/sequences/SingleNodeSequences.java
index 7813fb1492..58c5f024f9 100644
--- a/src/java/org/apache/cassandra/tcm/sequences/SingleNodeSequences.java
+++ b/src/java/org/apache/cassandra/tcm/sequences/SingleNodeSequences.java
@@ -21,6 +21,8 @@ package org.apache.cassandra.tcm.sequences;
 import java.util.Collections;
 import java.util.EnumSet;
 
+import javax.annotation.Nullable;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,6 +40,7 @@ import org.apache.cassandra.tcm.transformations.PrepareLeave;
 import org.apache.cassandra.tcm.transformations.PrepareMove;
 
 import static org.apache.cassandra.service.StorageService.Mode.LEAVING;
+import static org.apache.cassandra.service.StorageService.Mode.MOVE_FAILED;
 import static org.apache.cassandra.service.StorageService.Mode.NORMAL;
 import static 
org.apache.cassandra.service.StorageService.Mode.DECOMMISSION_FAILED;
 import static 
org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
@@ -96,6 +99,11 @@ public interface SingleNodeSequences
             StorageService.instance.shutdownNetworking();
     }
 
+    static void abortDecommission(String nodeId)
+    {
+        abortHelper(nodeId, MultiStepOperation.Kind.LEAVE, 
DECOMMISSION_FAILED);
+    }
+
     /**
      * Entrypoint to begin node removal process
      *
@@ -134,6 +142,11 @@ public interface SingleNodeSequences
         InProgressSequences.finishInProgressSequences(toRemove);
     }
 
+    static void abortRemoveNode(String nodeId)
+    {
+        abortHelper(nodeId, MultiStepOperation.Kind.REMOVE, null);
+    }
+
     /**
      * move the node to new token or find a new token to boot to according to 
load
      *
@@ -184,7 +197,7 @@ public interface SingleNodeSequences
             logger.info(msg);
             throw new IllegalStateException(msg);
         }
-        if (StorageService.instance.operationMode() != 
StorageService.Mode.MOVE_FAILED)
+        if (StorageService.instance.operationMode() != MOVE_FAILED)
         {
             String msg = "Can't resume a move operation unless it has failed";
             logger.info(msg);
@@ -194,28 +207,48 @@ public interface SingleNodeSequences
         InProgressSequences.finishInProgressSequences(self);
     }
 
-    static void abortMove()
+    static void abortMove(String nodeId)
+    {
+        abortHelper(nodeId, MultiStepOperation.Kind.MOVE, MOVE_FAILED);
+    }
+
+    /**
+     *
+     * @param nodeId node id to abort the MSO for, null for local node
+     * @param kind the expected kind of the multi step operation to abolt
+     * @param ssMode the legacy mode we want storage service to be in, null 
for any
+     */
+    private static void abortHelper(@Nullable String nodeId, 
MultiStepOperation.Kind kind, @Nullable StorageService.Mode ssMode)
     {
         if (ClusterMetadataService.instance().isMigrating() || 
ClusterMetadataService.state() == ClusterMetadataService.State.GOSSIP)
-            throw new IllegalStateException("This cluster is migrating to 
cluster metadata, can't move until that is done.");
+            throw new IllegalStateException(String.format("This cluster is 
migrating to cluster metadata, can't abort %s until that is done.", kind));
 
         ClusterMetadata metadata = ClusterMetadata.current();
-        NodeId self = metadata.myNodeId();
-        MultiStepOperation<?> sequence = 
metadata.inProgressSequences.get(self);
-        if (sequence == null || sequence.kind() != 
MultiStepOperation.Kind.MOVE)
+        NodeId toAbort = nodeId == null ? metadata.myNodeId() : 
NodeId.fromString(nodeId);
+        MultiStepOperation<?> sequence = 
metadata.inProgressSequences.get(toAbort);
+        if (sequence == null || sequence.kind() != kind)
         {
-            String msg = "No move operation in progress, can't abort";
+            String msg = String.format("No %s operation in progress for %s, 
can't abort (%s)", kind, toAbort, sequence);
             logger.info(msg);
             throw new IllegalStateException(msg);
         }
-        if (StorageService.instance.operationMode() != 
StorageService.Mode.MOVE_FAILED)
+        if (toAbort.equals(metadata.myNodeId()))
+        {
+            if (ssMode != null && StorageService.instance.operationMode() != 
ssMode)
+            {
+                String msg = String.format("Can't abort a %s operation unless 
it has failed", kind);
+                logger.info(msg);
+                throw new IllegalStateException(msg);
+            }
+            StorageService.instance.clearTransientMode();
+        }
+        else if 
(Gossiper.instance.isAlive(metadata.directory.endpoint(toAbort)))
         {
-            String msg = "Can't abort a move operation unless it has failed";
+            String msg = String.format("Can't abort a %s operation for a node 
%s (%s) that is UP - run abortdecommission on that instance",
+                                       kind, toAbort, 
metadata.directory.endpoint(toAbort));
             logger.info(msg);
             throw new IllegalStateException(msg);
         }
-        StorageService.instance.clearTransientMode();
-        ClusterMetadataService.instance().commit(new 
CancelInProgressSequence(self));
+        ClusterMetadataService.instance().commit(new 
CancelInProgressSequence(toAbort));
     }
-
 }
diff --git a/src/java/org/apache/cassandra/tools/NodeProbe.java 
b/src/java/org/apache/cassandra/tools/NodeProbe.java
index f7ac9eff3b..5a6e8b3598 100644
--- a/src/java/org/apache/cassandra/tools/NodeProbe.java
+++ b/src/java/org/apache/cassandra/tools/NodeProbe.java
@@ -1026,6 +1026,11 @@ public class NodeProbe implements AutoCloseable
         ssProxy.decommission(force);
     }
 
+    public void abortDecommission(String nodeId)
+    {
+        ssProxy.abortDecommission(nodeId);
+    }
+
     public void move(String newToken) throws IOException
     {
         ssProxy.move(newToken);
@@ -1036,9 +1041,9 @@ public class NodeProbe implements AutoCloseable
         ssProxy.resumeMove();
     }
 
-    public void abortMove()
+    public void abortMove(String nodeId)
     {
-        ssProxy.abortMove();
+        ssProxy.abortMove(nodeId);
     }
 
     public void removeNode(String token)
@@ -1051,6 +1056,11 @@ public class NodeProbe implements AutoCloseable
         ssProxy.removeNode(token, force);
     }
 
+    public void abortRemoveNode(String nodeId)
+    {
+        ssProxy.abortRemoveNode(nodeId);
+    }
+
     public String getRemovalStatus(boolean withPort)
     {
         return withPort ? ssProxy.getRemovalStatusWithPort() : 
ssProxy.getRemovalStatus();
diff --git a/src/java/org/apache/cassandra/tools/NodeTool.java 
b/src/java/org/apache/cassandra/tools/NodeTool.java
index bd1e302ba0..49be441b89 100644
--- a/src/java/org/apache/cassandra/tools/NodeTool.java
+++ b/src/java/org/apache/cassandra/tools/NodeTool.java
@@ -106,6 +106,7 @@ public class NodeTool
                 CompactionStats.class,
                 DataPaths.class,
                 Decommission.class,
+                Decommission.Abort.class,
                 DescribeCluster.class,
                 DescribeRing.class,
                 DisableAuditLog.class,
@@ -172,6 +173,7 @@ public class NodeTool
                 ListPendingHints.class,
                 ListSnapshots.class,
                 Move.class,
+                Move.Abort.class,
                 NetStats.class,
                 PauseHandoff.class,
                 ProfileLoad.class,
@@ -188,6 +190,7 @@ public class NodeTool
                 ReloadTriggers.class,
                 RelocateSSTables.class,
                 RemoveNode.class,
+                RemoveNode.Abort.class,
                 Repair.class,
                 ReplayBatchlog.class,
                 ResetFullQueryLog.class,
diff --git a/src/java/org/apache/cassandra/tools/nodetool/Decommission.java 
b/src/java/org/apache/cassandra/tools/nodetool/Decommission.java
index de70932c53..2c326c9b29 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/Decommission.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/Decommission.java
@@ -26,7 +26,6 @@ import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
 @Command(name = "decommission", description = "Decommission the *node I am 
connecting to*")
 public class Decommission extends NodeToolCmd
 {
-
     @Option(title = "force",
     name = {"-f", "--force"},
     description = "Force decommission of this node even when it reduces the 
number of replicas to below configured RF")
@@ -56,4 +55,17 @@ public class Decommission extends NodeToolCmd
             throw new IllegalStateException("Unsupported operation: " + 
e.getMessage(), e);
         }
     }
+
+    @Command(name = "abortdecommission", description = "Abort an ongoing, 
failed decommission")
+    public static class Abort extends NodeToolCmd
+    {
+        @Option(title = "node id", name = "--node")
+        private String nodeId;
+
+        @Override
+        protected void execute(NodeProbe probe)
+        {
+            probe.abortDecommission(nodeId);
+        }
+    }
 }
diff --git a/src/java/org/apache/cassandra/tools/nodetool/Move.java 
b/src/java/org/apache/cassandra/tools/nodetool/Move.java
index 87c085b86b..ef05bf89b6 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/Move.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/Move.java
@@ -36,9 +36,6 @@ public class Move extends NodeToolCmd
     @Option(title = "Resume an ongoing move operation", name = "--resume")
     private boolean resume;
 
-    @Option(title = "Abort an ongoing move operation", name = "--abort")
-    private boolean abort;
-
     @Override
     public void execute(NodeProbe probe)
     {
@@ -46,20 +43,12 @@ public class Move extends NodeToolCmd
         {
             if (!newToken.isEmpty())
             {
-                if (resume || abort)
-                    throw new IllegalArgumentException("Can't give both a 
token and --resume/--abort");
-
                 probe.move(newToken);
             }
             else
             {
-                if (abort && resume)
-                    throw new IllegalArgumentException("Can't both resume and 
abort");
-
                 if (resume)
                     probe.resumeMove();
-                else if (abort)
-                    probe.abortMove();
                 else
                     throw new IllegalArgumentException("Need to give either a 
token for a new move operation, or --resume/--abort for an existing one");
             }
@@ -68,4 +57,17 @@ public class Move extends NodeToolCmd
             throw new RuntimeException("Error during moving node", e);
         }
     }
+
+    @Command(name = "abortmove", description = "Abort a failed move operation 
for this or a remote node")
+    public static class Abort extends NodeToolCmd
+    {
+        @Option(title = "node id", name = "--node")
+        private String nodeId;
+
+        @Override
+        public void execute(NodeProbe probe)
+        {
+            probe.abortMove(nodeId);
+        }
+    }
 }
diff --git a/src/java/org/apache/cassandra/tools/nodetool/RemoveNode.java 
b/src/java/org/apache/cassandra/tools/nodetool/RemoveNode.java
index 89912ec640..94b3262058 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/RemoveNode.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/RemoveNode.java
@@ -22,13 +22,14 @@ import java.util.List;
 import io.airlift.airline.Arguments;
 import io.airlift.airline.Command;
 
+import io.airlift.airline.Option;
 import org.apache.cassandra.tools.NodeProbe;
 import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
 
 @Command(name = "removenode", description = "Show status of current node 
removal, abort removal or remove provided ID")
 public class RemoveNode extends NodeToolCmd
 {
-    @Arguments(title = "remove_operation", usage = "<status>|<abort> 
<ID>|<ID>|<ID> --force", description = "Show status of current node removal, 
abort removal, or remove provided ID", required = true)
+    @Arguments(title = "remove_operation", usage = "<status>|<ID>|<ID> 
--force", description = "Show status of current node removal, or remove 
provided ID", required = true)
     private List<String> removeOperation = null;
 
     @Override
@@ -41,15 +42,22 @@ public class RemoveNode extends NodeToolCmd
                 break;
             case "force":
                 throw new IllegalArgumentException("Can't force a nodetool 
removenode. Instead abort the ongoing removenode and retry.");
-            case "abort":
-                if (removeOperation.size() < 2)
-                    probe.output().err.print("Abort requires the node id to 
abort the removal for.");
-                
probe.getCMSOperationsProxy().cancelInProgressSequences(removeOperation.get(1), 
"REMOVE");
-                break;
             default:
                 boolean force = removeOperation.size() > 1 && 
removeOperation.get(1).equals("--force");
                 probe.removeNode(removeOperation.get(0), force);
                 break;
         }
     }
+
+    @Command(name = "abortremovenode", description = "Abort a removenode 
command")
+    public static class Abort extends NodeToolCmd
+    {
+        @Option(title = "node id", name="--node", description = "The node 
being removed", required = true)
+        private String nodeId;
+
+        public void execute(NodeProbe probe)
+        {
+            probe.abortRemoveNode(nodeId);
+        }
+    }
 }
diff --git 
a/test/distributed/org/apache/cassandra/distributed/test/FailingMoveTest.java 
b/test/distributed/org/apache/cassandra/distributed/test/FailingMoveTest.java
index ac60f90cd3..65e0c28c3a 100644
--- 
a/test/distributed/org/apache/cassandra/distributed/test/FailingMoveTest.java
+++ 
b/test/distributed/org/apache/cassandra/distributed/test/FailingMoveTest.java
@@ -20,8 +20,11 @@ package org.apache.cassandra.distributed.test;
 
 import java.io.IOException;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.Test;
 
 import net.bytebuddy.ByteBuddy;
@@ -32,6 +35,7 @@ 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.IInvokableInstance;
+import org.apache.cassandra.gms.Gossiper;
 import org.apache.cassandra.service.StorageService;
 import org.apache.cassandra.streaming.StreamPlan;
 import org.apache.cassandra.streaming.StreamResultFuture;
@@ -41,6 +45,7 @@ import org.apache.cassandra.tcm.membership.NodeId;
 import static net.bytebuddy.matcher.ElementMatchers.named;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 public class FailingMoveTest extends TestBaseImpl
 {
@@ -94,12 +99,49 @@ public class FailingMoveTest extends TestBaseImpl
                 BB.shouldFail.set(false);
             });
 
-            cluster.get(3).nodetoolResult("move", 
"--abort").asserts().success();
+            cluster.get(3).nodetoolResult("abortmove").asserts().success();
             cluster.get(3).runOnInstance(() -> 
assertEquals(StorageService.Mode.NORMAL, 
StorageService.instance.operationMode()));
             assertNotEquals(moveToToken, getToken(cluster.get(3)));
         }
     }
 
+    @Test
+    public void testAbortMoveRemote() throws IOException, ExecutionException, 
InterruptedException
+    {
+        try (Cluster cluster = init(Cluster.build(3)
+                                           .withoutVNodes()
+                                           .withConfig(c -> 
c.with(Feature.GOSSIP, Feature.NETWORK))
+                                           
.withInstanceInitializer(BB::install)
+                                           .start()))
+        {
+            cluster.schemaChange(withKeyspace("CREATE TABLE %s.tbl(id int 
primary key);"));
+            for (int i=0; i<30; i++)
+                cluster.coordinator(1).execute(withKeyspace("INSERT INTO 
%s.tbl (id) VALUES (?)"),
+                                               ConsistencyLevel.ALL, i);
+            String oldToken = getToken(cluster.get(3));
+            String moveToToken = "2305843009213693949";
+            assertNotEquals(oldToken, moveToToken);
+            cluster.get(3).nodetoolResult("move", 
moveToToken).asserts().failure();
+            int nodeId = cluster.get(3).callOnInstance(() -> {
+                assertEquals(StorageService.Mode.MOVE_FAILED, 
StorageService.instance.operationMode());
+                BB.shouldFail.set(false);
+                return ClusterMetadata.current().myNodeId().id();
+            });
+            cluster.get(3).shutdown().get();
+            cluster.get(2).runOnInstance(() -> {
+                while 
(Gossiper.instance.isAlive(ClusterMetadata.current().directory.endpoint(new 
NodeId(nodeId))))
+                    Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
+            });
+            cluster.get(2).nodetoolResult("abortmove", "--node", 
String.valueOf(nodeId)).asserts().success();
+            cluster.get(3).startup();
+            assertNotEquals(moveToToken, getToken(cluster.get(3)));
+            cluster.get(3).runOnInstance(() -> {
+                assertEquals(StorageService.Mode.NORMAL, 
StorageService.instance.operationMode());
+                
assertTrue(ClusterMetadata.current().inProgressSequences.isEmpty());
+            });
+        }
+    }
+
     private String getToken(IInvokableInstance instance)
     {
         return instance.callsOnInstance(() -> {
diff --git 
a/test/distributed/org/apache/cassandra/distributed/test/RemoveNodeTest.java 
b/test/distributed/org/apache/cassandra/distributed/test/RemoveNodeTest.java
index 2710fdc832..cb1514b2c8 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/RemoveNodeTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/RemoveNodeTest.java
@@ -79,7 +79,7 @@ public class RemoveNodeTest extends TestBaseImpl
 
             // Now abort the removal. This should succeed in committing a 
cancellation of the removal sequence before
             // it can be completed, as non-CMS instance is still paused.
-            cmsInstance.nodetoolResult("removenode", "abort", 
nodeId).asserts().success();
+            cmsInstance.nodetoolResult("abortremovenode", "--node", 
nodeId).asserts().success();
 
             // Resume processing on the non-CMS instance. It will enact the 
MID_LEAVE step followed by the cancellation
             // of the removal process.
diff --git 
a/test/distributed/org/apache/cassandra/distributed/test/ring/DecommissionTest.java
 
b/test/distributed/org/apache/cassandra/distributed/test/ring/DecommissionTest.java
index 9826d013d5..6f67dac03b 100644
--- 
a/test/distributed/org/apache/cassandra/distributed/test/ring/DecommissionTest.java
+++ 
b/test/distributed/org/apache/cassandra/distributed/test/ring/DecommissionTest.java
@@ -22,9 +22,12 @@ import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.annotation.Nullable;
 
+import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.Test;
 
 import net.bytebuddy.ByteBuddy;
@@ -37,9 +40,12 @@ import org.apache.cassandra.distributed.api.ConsistencyLevel;
 import org.apache.cassandra.distributed.api.NodeToolResult;
 import org.apache.cassandra.distributed.shared.ClusterUtils;
 import org.apache.cassandra.distributed.test.TestBaseImpl;
+import org.apache.cassandra.gms.Gossiper;
+import org.apache.cassandra.service.StorageService;
 import org.apache.cassandra.streaming.StreamSession;
 import org.apache.cassandra.tcm.ClusterMetadata;
 import org.apache.cassandra.tcm.ClusterMetadataService;
+import org.apache.cassandra.tcm.membership.NodeId;
 import org.apache.cassandra.tcm.membership.NodeVersion;
 import org.apache.cassandra.tcm.serialization.Version;
 import org.apache.cassandra.tcm.transformations.Startup;
@@ -94,6 +100,56 @@ public class DecommissionTest extends TestBaseImpl
         }
     }
 
+    @Test
+    public void testAbortDecom() throws IOException
+    {
+        try (Cluster cluster = builder().withNodes(3)
+                                        .withConfig(config -> 
config.with(NETWORK, GOSSIP))
+                                        .withInstanceInitializer(BB::install)
+                                        .start())
+        {
+            populate(cluster, 0, 100, 1, 2, ConsistencyLevel.QUORUM);
+            cluster.get(2).nodetoolResult("decommission", 
"--force").asserts().failure();
+            
cluster.get(2).nodetoolResult("abortdecommission").asserts().success();
+            cluster.get(2).runOnInstance(() -> {
+                assertEquals(StorageService.Mode.NORMAL, 
StorageService.instance.operationMode());
+                
assertTrue(ClusterMetadata.current().inProgressSequences.isEmpty());
+            });
+            cluster.get(2).nodetoolResult("decommission", 
"--force").asserts().success();
+        }
+    }
+
+    @Test
+    public void testAbortDecomRemote() throws IOException, ExecutionException, 
InterruptedException
+    {
+        try (Cluster cluster = builder().withNodes(3)
+                                        .withConfig(config -> 
config.with(NETWORK, GOSSIP))
+                                        .withInstanceInitializer(BB::install)
+                                        .start())
+        {
+            populate(cluster, 0, 100, 1, 2, ConsistencyLevel.QUORUM);
+            int nodeId = cluster.get(2).callOnInstance(() -> {
+                return ClusterMetadata.current().myNodeId().id();
+            });
+            cluster.get(2).nodetoolResult("decommission", 
"--force").asserts().failure();
+            cluster.get(2).shutdown().get();
+            cluster.get(3).runOnInstance(() -> {
+                while 
(Gossiper.instance.isAlive(ClusterMetadata.current().directory.endpoint(new 
NodeId(nodeId))))
+                    Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
+            });
+            cluster.get(3).nodetoolResult("abortdecommission", "--node", 
String.valueOf(nodeId)).asserts().success();
+            cluster.get(2).startup();
+            cluster.get(2).runOnInstance(() -> {
+                assertEquals(StorageService.Mode.NORMAL, 
StorageService.instance.operationMode());
+                
assertTrue(ClusterMetadata.current().inProgressSequences.isEmpty());
+            });
+            cluster.get(2).runOnInstance(() -> {
+                BB.first.set(true);
+            });
+            cluster.get(2).nodetoolResult("decommission", 
"--force").asserts().success();
+        }
+    }
+
     @Test
     public void testDecomDirectoryMinMaxVersions() throws IOException {
         try (Cluster cluster = builder()


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

Reply via email to