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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 7b58ce0cdaf6 CAMEL-25488: camel-jgroups-raft - tests close their 
channels and report the state of the nodes when no leader is elected (#28243)
7b58ce0cdaf6 is described below

commit 7b58ce0cdaf659e0f26597a4a8a29f8071311fce
Author: AurĂ©lien Pupier <[email protected]>
AuthorDate: Fri Oct 23 14:27:31 2026 +0200

    CAMEL-25488: camel-jgroups-raft - tests close their channels and report the 
state of the nodes when no leader is elected (#28243)
    
    The cluster tests never closed their channels when they failed. The
    channels stay
    registered in the static routing table of SHARED_LOOPBACK for the whole
    JVM, so
    every re-run of the test failed on joining with "join of A rejected as
    it would
    create a view with duplicate members". The re-runs were thus always
    failing with
    a different error than the first attempt, and hid the original failure.
    
    Track the channels created by the tests and close them (and stop the
    contexts)
    after each test, so that a re-run is a real re-run.
    
    Also, when waiting for a leader times out, report for each node whether
    it is
    connected, its leader, its term and its view, to find out why the
    election stalls.
    
    Co-authored-by: IBM Bob 2.2.1
    Signed-off-by: AurĂ©lien Pupier <[email protected]>
---
 .../jgroups/raft/JGroupsRaftAbstractTest.java      | 37 +++++-----
 .../component/jgroups/raft/RaftNodesState.java     | 44 ++++++++++++
 .../cluster/JGroupsRaftClusterAbstractTest.java    | 81 +++++++++++++++++-----
 .../JGroupsRaftClusteredRoutePolicyTest.java       | 52 ++++++--------
 .../raft/cluster/JGroupsRaftMasterTest.java        | 58 ++++++----------
 5 files changed, 173 insertions(+), 99 deletions(-)

diff --git 
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/JGroupsRaftAbstractTest.java
 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/JGroupsRaftAbstractTest.java
index aba49ba251ac..6792f939ae0b 100644
--- 
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/JGroupsRaftAbstractTest.java
+++ 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/JGroupsRaftAbstractTest.java
@@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.test.junit6.CamelTestSupport;
+import org.awaitility.core.ConditionTimeoutException;
 import org.jgroups.raft.RaftHandle;
 
 import static org.awaitility.Awaitility.await;
@@ -43,22 +44,26 @@ public abstract class JGroupsRaftAbstractTest extends 
CamelTestSupport {
      * address to forward to.
      */
     protected void waitForLeader(int attempts, RaftHandle... handles) {
-        await().atMost(attempts, TimeUnit.SECONDS)
-                .pollInterval(500, TimeUnit.MILLISECONDS)
-                .until(() -> {
-                    boolean hasLeader = false;
-                    for (RaftHandle rh : handles) {
-                        if (!rh.channel().isConnected()) {
-                            continue;
+        try {
+            await().atMost(attempts, TimeUnit.SECONDS)
+                    .pollInterval(500, TimeUnit.MILLISECONDS)
+                    .until(() -> {
+                        boolean hasLeader = false;
+                        for (RaftHandle rh : handles) {
+                            if (!rh.channel().isConnected()) {
+                                continue;
+                            }
+                            if (rh.isLeader()) {
+                                hasLeader = true;
+                            }
+                            if (rh.leader() == null) {
+                                return false;
+                            }
                         }
-                        if (rh.isLeader()) {
-                            hasLeader = true;
-                        }
-                        if (rh.leader() == null) {
-                            return false;
-                        }
-                    }
-                    return hasLeader;
-                });
+                        return hasLeader;
+                    });
+        } catch (ConditionTimeoutException e) {
+            throw new ConditionTimeoutException(e.getMessage() + 
System.lineSeparator() + RaftNodesState.describe(handles), e);
+        }
     }
 }
diff --git 
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/RaftNodesState.java
 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/RaftNodesState.java
new file mode 100644
index 000000000000..8cfb5ec8e3b7
--- /dev/null
+++ 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/RaftNodesState.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.jgroups.raft;
+
+import org.jgroups.JChannel;
+import org.jgroups.raft.RaftHandle;
+
+public final class RaftNodesState {
+
+    private RaftNodesState() {
+    }
+
+    /**
+     * Describe the state of the given nodes, to find out why no leader has 
been elected.
+     */
+    public static String describe(RaftHandle... handles) {
+        StringBuilder sb = new StringBuilder("State of the nodes when giving 
up waiting for a leader:");
+        for (RaftHandle rh : handles) {
+            JChannel channel = rh.channel();
+            sb.append(System.lineSeparator())
+                    .append("  raftId=").append(rh.raftId())
+                    .append(" connected=").append(channel.isConnected())
+                    .append(" isLeader=").append(rh.isLeader())
+                    .append(" leader=").append(rh.leader())
+                    .append(" term=").append(rh.currentTerm())
+                    .append(" view=").append(channel.getView());
+        }
+        return sb.toString();
+    }
+}
diff --git 
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusterAbstractTest.java
 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusterAbstractTest.java
index 62e0901db6d7..e77ef2799ae3 100644
--- 
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusterAbstractTest.java
+++ 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusterAbstractTest.java
@@ -16,15 +16,60 @@
  */
 package org.apache.camel.component.jgroups.raft.cluster;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.jgroups.raft.RaftNodesState;
+import org.awaitility.core.ConditionTimeoutException;
 import org.jgroups.JChannel;
 import org.jgroups.raft.RaftHandle;
+import org.jgroups.raft.StateMachine;
+import org.junit.jupiter.api.AfterEach;
 
 import static org.awaitility.Awaitility.await;
 
 public abstract class JGroupsRaftClusterAbstractTest {
 
+    protected final List<CamelContext> contexts = new ArrayList<>();
+    protected final List<String> routeNames = new ArrayList<>();
+    private final List<RaftHandle> openHandles = new ArrayList<>();
+
+    /**
+     * The channels are registered in the static routing table of 
SHARED_LOOPBACK, so a channel left open by a failed
+     * test would make a rerun of the test fail on joining with the same 
member names ("duplicate members"), and would
+     * hide the reason of the first failure.
+     */
+    @AfterEach
+    void closeAll() {
+        for (CamelContext context : contexts) {
+            try {
+                context.stop();
+            } catch (Exception e) {
+                // best effort cleanup
+            }
+        }
+        for (RaftHandle handle : openHandles) {
+            JChannel channel = handle.channel();
+            if (channel != null) {
+                channel.close();
+            }
+        }
+        contexts.clear();
+        routeNames.clear();
+        openHandles.clear();
+    }
+
+    /**
+     * Create the Raft handle of a node and keep track of it, so that its 
channel is closed at the end of the test.
+     */
+    protected RaftHandle createHandle(String id, StateMachine stateMachine) 
throws Exception {
+        RaftHandle handle = new RaftHandle(new 
JChannel("raftABC.xml").name(id), stateMachine).raftId(id);
+        openHandles.add(handle);
+        return handle;
+    }
+
     /**
      * Wait until a leader has been elected AND all given handles know who the 
leader is. Only connected handles are
      * checked; disconnected or closed handles are skipped. Without checking 
leader() on every active handle, a follower
@@ -32,23 +77,27 @@ public abstract class JGroupsRaftClusterAbstractTest {
      * address to forward to.
      */
     protected void waitForLeader(int attempts, RaftHandle... handles) {
-        await().atMost(attempts, TimeUnit.SECONDS)
-                .pollInterval(500, TimeUnit.MILLISECONDS)
-                .until(() -> {
-                    boolean hasLeader = false;
-                    for (RaftHandle rh : handles) {
-                        if (!rh.channel().isConnected()) {
-                            continue;
-                        }
-                        if (rh.isLeader()) {
-                            hasLeader = true;
-                        }
-                        if (rh.leader() == null) {
-                            return false;
+        try {
+            await().atMost(attempts, TimeUnit.SECONDS)
+                    .pollInterval(500, TimeUnit.MILLISECONDS)
+                    .until(() -> {
+                        boolean hasLeader = false;
+                        for (RaftHandle rh : handles) {
+                            if (!rh.channel().isConnected()) {
+                                continue;
+                            }
+                            if (rh.isLeader()) {
+                                hasLeader = true;
+                            }
+                            if (rh.leader() == null) {
+                                return false;
+                            }
                         }
-                    }
-                    return hasLeader;
-                });
+                        return hasLeader;
+                    });
+        } catch (ConditionTimeoutException e) {
+            throw new ConditionTimeoutException(e.getMessage() + 
System.lineSeparator() + RaftNodesState.describe(handles), e);
+        }
     }
 
     /**
diff --git 
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusteredRoutePolicyTest.java
 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusteredRoutePolicyTest.java
index f91b85872d1e..4acf48192d21 100644
--- 
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusteredRoutePolicyTest.java
+++ 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftClusteredRoutePolicyTest.java
@@ -16,7 +16,7 @@
  */
 package org.apache.camel.component.jgroups.raft.cluster;
 
-import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.camel.CamelContext;
@@ -25,7 +25,6 @@ import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.jgroups.raft.utils.NopStateMachine;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.impl.cluster.ClusteredRoutePolicy;
-import org.jgroups.JChannel;
 import org.jgroups.raft.RaftHandle;
 import org.junit.jupiter.api.Test;
 
@@ -34,33 +33,24 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class JGroupsRaftClusteredRoutePolicyTest extends 
JGroupsRaftClusterAbstractTest {
 
-    private ArrayList<CamelContext> lcc = new ArrayList<>();
-    private ArrayList<String> rn = new ArrayList<>();
-
     @Test
     @SuppressWarnings("resource")
     public void test() throws Exception {
-        // NOTE: to be closed by component lifecycle.
-        JChannel chA = new JChannel("raftABC.xml").name("A");
-        RaftHandle handleA = new RaftHandle(chA, new 
NopStateMachine()).raftId("A");
+        RaftHandle handleA = createHandle("A", new NopStateMachine());
         CamelContext contextA = createContext("A", handleA);
 
-        // NOTE: to be closed by component lifecycle.
-        JChannel chB = new JChannel("raftABC.xml").name("B");
-        RaftHandle handleB = new RaftHandle(chB, new 
NopStateMachine()).raftId("B");
+        RaftHandle handleB = createHandle("B", new NopStateMachine());
         CamelContext contextB = createContext("B", handleB);
 
-        // NOTE: to be closed by component lifecycle.
-        JChannel chC = new JChannel("raftABC.xml").name("C");
-        RaftHandle handleC = new RaftHandle(chC, new 
NopStateMachine()).raftId("C");
+        RaftHandle handleC = createHandle("C", new NopStateMachine());
         CamelContext contextC = createContext("C", handleC);
 
-        lcc.add(contextA);
-        rn.add("route-A");
-        lcc.add(contextB);
-        rn.add("route-B");
-        lcc.add(contextC);
-        rn.add("route-C");
+        contexts.add(contextA);
+        routeNames.add("route-A");
+        contexts.add(contextB);
+        routeNames.add("route-B");
+        contexts.add(contextC);
+        routeNames.add("route-C");
 
         contextA.start();
         contextB.start();
@@ -70,18 +60,17 @@ public class JGroupsRaftClusteredRoutePolicyTest extends 
JGroupsRaftClusterAbstr
 
         contextA.stop();
         // Ensure channel A is fully closed before checking for a new leader
-        chA.close();
+        handleA.channel().close();
         waitForLeader(50, handleB, handleC);
         awaitOneActiveRoute();
 
         contextB.stop();
         // Ensure channel B is fully closed before creating a new channel with 
the same member name
-        chB.close();
-        waitForViewSize(chC, 1, 30);
-        chA = new JChannel("raftABC.xml").name("A");
-        handleA = new RaftHandle(chA, new NopStateMachine()).raftId("A");
+        handleB.channel().close();
+        waitForViewSize(handleC.channel(), 1, 30);
+        handleA = createHandle("A", new NopStateMachine());
         contextA = createContext("A", handleA);
-        lcc.set(0, contextA);
+        contexts.set(0, contextA);
         contextA.start();
         waitForLeader(50, handleA, handleC);
         awaitOneActiveRoute();
@@ -113,17 +102,18 @@ public class JGroupsRaftClusteredRoutePolicyTest extends 
JGroupsRaftClusterAbstr
 
     private void awaitOneActiveRoute() {
         // the policy starts and stops the routes on its own thread, shortly 
after the leadership change
-        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> 
assertEquals(1, countActiveFromEndpoints(lcc, rn)));
+        await().atMost(10, TimeUnit.SECONDS)
+                .untilAsserted(() -> assertEquals(1, 
countActiveFromEndpoints(contexts, routeNames)));
     }
 
-    private int countActiveFromEndpoints(ArrayList<CamelContext> lcc, 
ArrayList<String> rn) {
+    private int countActiveFromEndpoints(List<CamelContext> contexts, 
List<String> routeNames) {
         int result = 0;
-        if (lcc.size() != rn.size()) {
+        if (contexts.size() != routeNames.size()) {
             throw new UnsupportedOperationException(
                     "CamelContext list and Route ids list must have the same 
number of elements!");
         }
-        for (int i = 0; i < lcc.size(); i++) {
-            ServiceStatus status = 
lcc.get(i).getRouteController().getRouteStatus(rn.get(i));
+        for (int i = 0; i < contexts.size(); i++) {
+            ServiceStatus status = 
contexts.get(i).getRouteController().getRouteStatus(routeNames.get(i));
             if (ServiceStatus.Starting.equals(status) || 
ServiceStatus.Started.equals(status)) {
                 result++;
             }
diff --git 
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftMasterTest.java
 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftMasterTest.java
index 5ca1ac5f1567..1a9637880783 100644
--- 
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftMasterTest.java
+++ 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/cluster/JGroupsRaftMasterTest.java
@@ -17,7 +17,7 @@
 package org.apache.camel.component.jgroups.raft.cluster;
 
 import java.lang.reflect.Field;
-import java.util.ArrayList;
+import java.util.List;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Route;
@@ -26,7 +26,6 @@ import 
org.apache.camel.component.jgroups.raft.utils.NopStateMachine;
 import org.apache.camel.component.master.MasterConsumer;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.awaitility.Awaitility;
-import org.jgroups.JChannel;
 import org.jgroups.raft.RaftHandle;
 import org.junit.jupiter.api.Test;
 
@@ -34,58 +33,45 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class JGroupsRaftMasterTest extends JGroupsRaftClusterAbstractTest {
 
-    private ArrayList<CamelContext> lcc = new ArrayList<>();
-    private ArrayList<String> rn = new ArrayList<>();
-
     @Test
     public void test() throws Exception {
-        @SuppressWarnings("resource")
-        // NOTE: to be closed by component lifecycle.
-        JChannel chA = new JChannel("raftABC.xml").name("A");
-        RaftHandle handleA = new RaftHandle(chA, new 
NopStateMachine()).raftId("A");
+        RaftHandle handleA = createHandle("A", new NopStateMachine());
         CamelContext contextA = createContext("A", handleA);
 
-        @SuppressWarnings("resource")
-        // NOTE: to be closed by component lifecycle.
-        JChannel chB = new JChannel("raftABC.xml").name("B");
-        RaftHandle handleB = new RaftHandle(chB, new 
NopStateMachine()).raftId("B");
+        RaftHandle handleB = createHandle("B", new NopStateMachine());
         CamelContext contextB = createContext("B", handleB);
 
-        @SuppressWarnings("resource")
-        // NOTE: to be closed by component lifecycle.
-        JChannel chC = new JChannel("raftABC.xml").name("C");
-        RaftHandle handleC = new RaftHandle(chC, new 
NopStateMachine()).raftId("C");
+        RaftHandle handleC = createHandle("C", new NopStateMachine());
         CamelContext contextC = createContext("C", handleC);
 
-        lcc.add(contextA);
-        rn.add("route-A");
-        lcc.add(contextB);
-        rn.add("route-B");
-        lcc.add(contextC);
-        rn.add("route-C");
+        contexts.add(contextA);
+        routeNames.add("route-A");
+        contexts.add(contextB);
+        routeNames.add("route-B");
+        contexts.add(contextC);
+        routeNames.add("route-C");
 
         contextA.start();
         contextB.start();
         contextC.start();
         waitForLeader(50, handleA, handleB, handleC);
-        Awaitility.await().untilAsserted(() -> assertEquals(1, 
countActiveFromEndpoints(lcc, rn)));
+        Awaitility.await().untilAsserted(() -> assertEquals(1, 
countActiveFromEndpoints(contexts, routeNames)));
 
         contextA.stop();
-        chA.close();
+        handleA.channel().close();
         waitForLeader(50, handleB, handleC);
-        Awaitility.await().untilAsserted(() -> assertEquals(1, 
countActiveFromEndpoints(lcc, rn)));
+        Awaitility.await().untilAsserted(() -> assertEquals(1, 
countActiveFromEndpoints(contexts, routeNames)));
 
         contextB.stop();
-        chB.close();
-        waitForViewSize(chC, 1, 30);
+        handleB.channel().close();
+        waitForViewSize(handleC.channel(), 1, 30);
         // Create a completely new channel, handle and context for A
-        chA = new JChannel("raftABC.xml").name("A");
-        handleA = new RaftHandle(chA, new NopStateMachine()).raftId("A");
+        handleA = createHandle("A", new NopStateMachine());
         contextA = createContext("A", handleA);
-        lcc.set(0, contextA);
+        contexts.set(0, contextA);
         contextA.start();
         waitForLeader(50, handleA, handleC);
-        Awaitility.await().untilAsserted(() -> assertEquals(1, 
countActiveFromEndpoints(lcc, rn)));
+        Awaitility.await().untilAsserted(() -> assertEquals(1, 
countActiveFromEndpoints(contexts, routeNames)));
     }
 
     private CamelContext createContext(String id, RaftHandle rh) throws 
Exception {
@@ -111,15 +97,15 @@ public class JGroupsRaftMasterTest extends 
JGroupsRaftClusterAbstractTest {
         return context;
     }
 
-    private int countActiveFromEndpoints(ArrayList<CamelContext> lcc, 
ArrayList<String> rn)
+    private int countActiveFromEndpoints(List<CamelContext> contexts, 
List<String> routeNames)
             throws NoSuchFieldException, IllegalAccessException {
         int result = 0;
-        if (lcc.size() != rn.size()) {
+        if (contexts.size() != routeNames.size()) {
             throw new UnsupportedOperationException(
                     "CamelContext list and Route ids list must have the same 
number of elements!");
         }
-        for (int i = 0; i < lcc.size(); i++) {
-            Route route = lcc.get(i).getRoute(rn.get(i));
+        for (int i = 0; i < contexts.size(); i++) {
+            Route route = contexts.get(i).getRoute(routeNames.get(i));
             if (route != null) {
                 MasterConsumer mc = (MasterConsumer) route.getConsumer();
                 Field delegatedConsumer = 
mc.getClass().getDeclaredField("delegatedConsumer");

Reply via email to