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 10f69b4430ee CAMEL-25515: camel-jgroups-raft - the consumer handles
the Learner role (#27870)
10f69b4430ee is described below
commit 10f69b4430eeb184b5fb4c4bb3ac8b5c23478ba2
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Oct 15 14:00:48 2026 +0200
CAMEL-25515: camel-jgroups-raft - the consumer handles the Learner role
(#27870)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
.../camel/catalog/docs/jgroups-raft-component.adoc | 4 ++
.../src/main/docs/jgroups-raft-component.adoc | 4 ++
.../jgroups/raft/CamelRoleChangeListener.java | 30 ++++++++++----
.../jgroups/raft/CamelRoleChangeListenerTest.java | 46 ++++++++++++++++++++++
4 files changed, 77 insertions(+), 7 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jgroups-raft-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jgroups-raft-component.adoc
index f10c5a39eb24..3a9893ca4ce7 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jgroups-raft-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jgroups-raft-component.adoc
@@ -164,6 +164,10 @@ YAML::
----
====
+The role change is forwarded as a `LEADER` or `FOLLOWER` event. A `Learner` (a
non-voting member that replicates the
+log but cannot vote or become leader) is reported as a `FOLLOWER` event, so a
route may receive consecutive `FOLLOWER`
+events, for example when a node steps down from leader to learner and again
when it becomes a voting follower.
+
=== Keeping singleton route within the cluster
The snippet below demonstrates how to keep the singleton consumer route
diff --git
a/components/camel-jgroups-raft/src/main/docs/jgroups-raft-component.adoc
b/components/camel-jgroups-raft/src/main/docs/jgroups-raft-component.adoc
index f10c5a39eb24..3a9893ca4ce7 100644
--- a/components/camel-jgroups-raft/src/main/docs/jgroups-raft-component.adoc
+++ b/components/camel-jgroups-raft/src/main/docs/jgroups-raft-component.adoc
@@ -164,6 +164,10 @@ YAML::
----
====
+The role change is forwarded as a `LEADER` or `FOLLOWER` event. A `Learner` (a
non-voting member that replicates the
+log but cannot vote or become leader) is reported as a `FOLLOWER` event, so a
route may receive consecutive `FOLLOWER`
+events, for example when a node steps down from leader to learner and again
when it becomes a voting follower.
+
=== Keeping singleton route within the cluster
The snippet below demonstrates how to keep the singleton consumer route
diff --git
a/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListener.java
b/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListener.java
index a5d143d88d1b..04bc47315708 100644
---
a/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListener.java
+++
b/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListener.java
@@ -42,18 +42,34 @@ public class CamelRoleChangeListener implements
RAFT.RoleChange {
@Override
public void roleChanged(Role role) {
LOG.trace("New Role {} received.", role);
+ JGroupsRaftEventType eventType = toEventType(role);
+ if (eventType == null) {
+ // Guard against future jgroups-raft Role values: log and ignore
rather than throw. RAFT swallows an
+ // exception thrown from this callback, so throwing would leave
the consumer silently without an event.
+ LOG.warn("Ignoring jgroups-raft role change to unhandled role
[{}]", role);
+ return;
+ }
Exchange exchange = createExchange();
+
exchange.getIn().setHeader(JGroupsRaftConstants.HEADER_JGROUPSRAFT_EVENT_TYPE,
eventType);
+ processExchange(role, exchange);
+ }
+
+ /**
+ * Maps a jgroups-raft {@link Role} to the {@link JGroupsRaftEventType}
dispatched to the route, or {@code null} for
+ * a role that produces no event. A {@code Learner} is a non-voting
follower (it replicates the log but cannot vote
+ * or become leader), so it maps to {@link JGroupsRaftEventType#FOLLOWER}:
a route that stops its leader work on a
+ * {@code FOLLOWER} event then also stops it when this node steps down to
{@code Learner} (CAMEL-25515). This
+ * mirrors the cluster-side handling added for CAMEL-25488.
+ */
+ static JGroupsRaftEventType toEventType(Role role) {
switch (role) {
case Leader:
-
exchange.getIn().setHeader(JGroupsRaftConstants.HEADER_JGROUPSRAFT_EVENT_TYPE,
JGroupsRaftEventType.LEADER);
- processExchange(role, exchange);
- break;
+ return JGroupsRaftEventType.LEADER;
case Follower:
-
exchange.getIn().setHeader(JGroupsRaftConstants.HEADER_JGROUPSRAFT_EVENT_TYPE,
JGroupsRaftEventType.FOLLOWER);
- processExchange(role, exchange);
- break;
+ case Learner:
+ return JGroupsRaftEventType.FOLLOWER;
default:
- throw new JGroupsRaftException("Role [" + role + "] unknown.");
+ return null;
}
}
diff --git
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListenerTest.java
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListenerTest.java
new file mode 100644
index 000000000000..27f19b1c85a1
--- /dev/null
+++
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListenerTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.protocols.raft.Role;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Unit tests for {@link CamelRoleChangeListener#toEventType(Role)}
(CAMEL-25515).
+ */
+class CamelRoleChangeListenerTest {
+
+ @Test
+ void leaderMapsToLeaderEvent() {
+ assertEquals(JGroupsRaftEventType.LEADER,
CamelRoleChangeListener.toEventType(Role.Leader));
+ }
+
+ @Test
+ void followerMapsToFollowerEvent() {
+ assertEquals(JGroupsRaftEventType.FOLLOWER,
CamelRoleChangeListener.toEventType(Role.Follower));
+ }
+
+ @Test
+ void learnerMapsToFollowerEvent() {
+ // CAMEL-25515: a learner is a non-voting follower; it must produce a
FOLLOWER event (previously it hit the
+ // default branch and threw, which RAFT swallowed, so the consumer got
no event and a route that steps down
+ // on FOLLOWER kept acting as leader after the node was moved to
Learner).
+ assertEquals(JGroupsRaftEventType.FOLLOWER,
CamelRoleChangeListener.toEventType(Role.Learner));
+ }
+}