Move cluster.notifications 17/116017/10
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 23 Mar 2025 04:19:48 +0000 (05:19 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 23 Mar 2025 12:03:40 +0000 (13:03 +0100)
Let's rehost these into sal-akka-raft for now, as that is where they are
used from. This allows us to tie 'role' with 'RaftState', leading to
improved type safety.

JIRA: CONTROLLER-2134
Change-Id: Icb4968774d89517486c50bff95393e8c68b8265b
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
18 files changed:
opendaylight/md-sal/sal-akka-raft-example/src/main/java/org/opendaylight/controller/cluster/example/ExampleRoleChangeListener.java
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/notifications/DefaultLeaderStateChanged.java [moved from opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/notifications/DefaultLeaderStateChanged.java with 100% similarity]
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/notifications/ForwadingLeaderStateChanged.java [moved from opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/notifications/ForwadingLeaderStateChanged.java with 100% similarity]
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/notifications/LeaderStateChanged.java [moved from opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/notifications/LeaderStateChanged.java with 100% similarity]
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/notifications/MemberNotication.java [moved from opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/notifications/MemberNotication.java with 100% similarity]
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/notifications/RegisterRoleChangeListener.java [moved from opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/notifications/RegisterRoleChangeListener.java with 100% similarity]
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/notifications/RegisterRoleChangeListenerReply.java [moved from opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/notifications/RegisterRoleChangeListenerReply.java with 100% similarity]
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/notifications/RoleChangeNotification.java [moved from opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/notifications/RoleChangeNotification.java with 53% similarity]
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/notifications/RoleChangeNotifier.java [moved from opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/notifications/RoleChangeNotifier.java with 97% similarity]
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/notifications/RoleChanged.java [moved from opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/notifications/RoleChanged.java with 72% similarity]
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java
opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftState.java
opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/IsolationScenarioTest.java
opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/PreLeaderScenarioTest.java
opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java
opendaylight/md-sal/sal-akka-raft/src/test/notifications/RoleChangeNotifierTest.java [moved from opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/notifications/RoleChangeNotifierTest.java with 100% similarity]
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/shardmanager/ShardManager.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/RoleChangeNotifierTest.java

index 17af28ae091bf9dce571ca4ce59555a9ada1254b..4c668d4bc4a3d9174d75343ad51117a6147618d4 100644 (file)
@@ -73,11 +73,10 @@ public class ExampleRoleChangeListener extends AbstractUntypedActor implements A
         } else if (message instanceof RoleChangeNotification notification) {
             // called by the Notifier
             LOG.info("Role Change Notification received for member:{}, old role:{}, new role:{}",
-                notification.getMemberId(), notification.getOldRole(), notification.getNewRole());
+                notification.memberId(), notification.oldRole(), notification.newRole());
 
             // the apps dependent on such notifications can be called here
             //TODO: add implementation here
-
         }
     }
 
@@ -8,34 +8,30 @@
 
 package org.opendaylight.controller.cluster.notifications;
 
+import static java.util.Objects.requireNonNull;
+
 import java.io.Serializable;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.controller.cluster.raft.RaftState;
 
 /**
  * Notification message representing a Role change of a cluster member.
  * Roles generally are Leader, Follower and Candidate. But can be based on the consensus strategy/implementation.
  * The Listener could be in a separate ActorSystem and hence this message needs to be Serializable.
  */
-public class RoleChangeNotification implements Serializable {
+@NonNullByDefault
+public record RoleChangeNotification(String memberId, @Nullable String oldRole, String newRole)
+        implements Serializable {
+    @java.io.Serial
     private static final long serialVersionUID = -2873869509490117116L;
-    private final String memberId;
-    private final String oldRole;
-    private final String newRole;
-
-    public RoleChangeNotification(String memberId, String oldRole, String newRole) {
-        this.memberId = memberId;
-        this.oldRole = oldRole;
-        this.newRole = newRole;
-    }
-
-    public String getMemberId() {
-        return memberId;
-    }
 
-    public String getOldRole() {
-        return oldRole;
+    public RoleChangeNotification {
+        requireNonNull(memberId);
+        requireNonNull(newRole);
     }
 
-    public String getNewRole() {
-        return newRole;
+    public RoleChangeNotification(final String memberId, final RaftState newRole, final @Nullable RaftState oldRole) {
+        this(memberId, oldRole != null ? oldRole.name() : null, newRole.name());
     }
 }
@@ -83,8 +83,8 @@ public class RoleChangeNotifier extends AbstractUntypedActor implements AutoClos
             LOG.info("RoleChangeNotifier for {} , received role change from {} to {}", memberId,
                 roleChanged.oldRole(), roleChanged.newRole());
 
-            latestRoleChangeNotification = new RoleChangeNotification(roleChanged.memberId(), roleChanged.oldRole(),
-                roleChanged.newRole());
+            latestRoleChangeNotification = new RoleChangeNotification(roleChanged.memberId(), roleChanged.newRole(),
+                roleChanged.oldRole());
 
             for (var listener : registeredListeners.values()) {
                 listener.tell(latestRoleChangeNotification, self());
@@ -11,15 +11,20 @@ import static java.util.Objects.requireNonNull;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.controller.cluster.raft.RaftState;
 
 /**
  * Role Change message initiated internally from the  Raft Actor when a the behavior/role changes.
  * Since its internal, need not be serialized.
  */
 @NonNullByDefault
-public record RoleChanged(String memberId, @Nullable String oldRole, String newRole) implements MemberNotication {
+public record RoleChanged(String memberId, RaftState newRole, @Nullable RaftState oldRole) implements MemberNotication {
     public RoleChanged {
         requireNonNull(memberId);
         requireNonNull(newRole);
     }
+
+    public RoleChanged(final String memberId, final RaftState newRole) {
+        this(memberId, newRole, null);
+    }
 }
index a1784fae8a31de3bf051930ffa6a326a8591b028..9885a7e7a0b087957df2a1db7a352e86f9470712 100644 (file)
@@ -537,15 +537,13 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor {
     }
 
     private void handleBehaviorChange(final BehaviorState oldBehaviorState, final RaftActorBehavior currentBehavior) {
-        RaftActorBehavior oldBehavior = oldBehaviorState.getBehavior();
-
+        final var oldBehavior = oldBehaviorState.getBehavior();
         if (oldBehavior != currentBehavior) {
             onStateChanged();
         }
 
-        String lastLeaderId = oldBehavior == null ? null : oldBehaviorState.getLastLeaderId();
-        String lastValidLeaderId = oldBehavior == null ? null : oldBehaviorState.getLastValidLeaderId();
-        String oldBehaviorStateName = oldBehavior == null ? null : oldBehavior.state().name();
+        final var lastLeaderId = oldBehavior == null ? null : oldBehaviorState.getLastLeaderId();
+        final var lastValidLeaderId = oldBehavior == null ? null : oldBehaviorState.getLastValidLeaderId();
 
         // it can happen that the state has not changed but the leader has changed.
         final var leaderId = currentBehavior.getLeaderId();
@@ -567,10 +565,24 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor {
             serverConfigurationSupport.onNewLeader(leaderId);
         }
 
-        if (roleChangeNotifier != null && (oldBehavior == null || oldBehavior.state() != currentBehavior.state())) {
-            roleChangeNotifier.tell(new RoleChanged(memberId(), oldBehaviorStateName,
-                currentBehavior.state().name()), self());
+        if (roleChangeNotifier != null) {
+            notifyRoleChange(roleChangeNotifier, currentBehavior.state(), oldBehavior);
+        }
+    }
+
+    @NonNullByDefault
+    private void notifyRoleChange(final ActorRef target, final RaftState newState,
+            final @Nullable RaftActorBehavior oldBehavior) {
+        final RaftState oldState;
+        if (oldBehavior != null) {
+            oldState = oldBehavior.state();
+            if (newState.equals(oldState)) {
+                return;
+            }
+        } else {
+            oldState = null;
         }
+        target.tell(new RoleChanged(memberId(), newState, oldState), self());
     }
 
     private void handleApplyState(final ApplyState applyState) {
index 3ec4d2237b36b3ce23d588de799333f4e3b0e1e3..e8c62233992e4708028a23aa3fff36eecf29cb77 100644 (file)
@@ -5,7 +5,6 @@
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-
 package org.opendaylight.controller.cluster.raft;
 
 public enum RaftState {
index 3968afb07b09ed3fc0f293c00c79d8d10160c338..e4a535b2286316434a356978a79ce851d1d88757 100644 (file)
@@ -79,7 +79,7 @@ public class IsolationScenarioTest extends AbstractRaftActorIntegrationTest {
         // The leader should transition to IsolatedLeader.
 
         expectFirstMatching(leaderNotifierActor, RoleChanged.class,
-            rc -> rc.newRole().equals(RaftState.IsolatedLeader.name()));
+            rc -> rc.newRole().equals(RaftState.IsolatedLeader));
 
         forceElectionOnFollower1();
 
@@ -106,8 +106,7 @@ public class IsolationScenarioTest extends AbstractRaftActorIntegrationTest {
         // Previous leader should switch to follower b/c it will receive either an AppendEntries or AppendEntriesReply
         // with a higher term.
 
-        expectFirstMatching(leaderNotifierActor, RoleChanged.class,
-            rc -> rc.newRole().equals(RaftState.Follower.name()));
+        expectFirstMatching(leaderNotifierActor, RoleChanged.class, rc -> rc.newRole().equals(RaftState.Follower));
 
         // The previous leader has a conflicting log entry at index 2 with a different term which should get
         // replaced by the new leader's index 1 entry.
@@ -187,7 +186,7 @@ public class IsolationScenarioTest extends AbstractRaftActorIntegrationTest {
         // The leader should transition to IsolatedLeader.
 
         expectFirstMatching(leaderNotifierActor, RoleChanged.class,
-            rc -> rc.newRole().equals(RaftState.IsolatedLeader.name()));
+            rc -> rc.newRole().equals(RaftState.IsolatedLeader));
 
         forceElectionOnFollower1();
 
@@ -215,8 +214,7 @@ public class IsolationScenarioTest extends AbstractRaftActorIntegrationTest {
         // Previous leader should switch to follower b/c it will receive either an AppendEntries or AppendEntriesReply
         // with a higher term.
 
-        expectFirstMatching(leaderNotifierActor, RoleChanged.class,
-            rc -> rc.newRole().equals(RaftState.Follower.name()));
+        expectFirstMatching(leaderNotifierActor, RoleChanged.class, rc -> rc.newRole().equals(RaftState.Follower));
 
         // The previous leader has a conflicting log entry at index 2 with a different term which should get
         // replaced by the new leader's entry.
@@ -315,7 +313,7 @@ public class IsolationScenarioTest extends AbstractRaftActorIntegrationTest {
         // The leader should transition to IsolatedLeader.
 
         expectFirstMatching(leaderNotifierActor, RoleChanged.class,
-            rc -> rc.newRole().equals(RaftState.IsolatedLeader.name()));
+            rc -> rc.newRole().equals(RaftState.IsolatedLeader));
 
         forceElectionOnFollower1();
 
@@ -345,8 +343,7 @@ public class IsolationScenarioTest extends AbstractRaftActorIntegrationTest {
         // Previous leader should switch to follower b/c it will receive either an AppendEntries or AppendEntriesReply
         // with a higher term.
 
-        expectFirstMatching(leaderNotifierActor, RoleChanged.class,
-            rc -> rc.newRole().equals(RaftState.Follower.name()));
+        expectFirstMatching(leaderNotifierActor, RoleChanged.class, rc -> rc.newRole().equals(RaftState.Follower));
 
         // The previous leader has conflicting log entries starting at index 2 with different terms which should get
         // replaced by the new leader's entries.
@@ -399,8 +396,7 @@ public class IsolationScenarioTest extends AbstractRaftActorIntegrationTest {
 
         follower1Actor.tell(TimeoutNow.INSTANCE, ActorRef.noSender());
 
-        expectFirstMatching(follower1NotifierActor, RoleChanged.class,
-            rc -> rc.newRole().equals(RaftState.Leader.name()));
+        expectFirstMatching(follower1NotifierActor, RoleChanged.class, rc -> rc.newRole().equals(RaftState.Leader));
 
         currentTerm = follower1Context.currentTerm();
     }
index 06cdc0f022e77f6538591c0ff82b69c3f0d6d70d..587c87aed6da0cd5a939f04ceec9f51bfc91ec2f 100644 (file)
@@ -75,9 +75,9 @@ public class PreLeaderScenarioTest extends AbstractRaftActorIntegrationTest {
 
         // Verify the expected raft state changes. It should go to PreLeader since it has an uncommitted entry.
         List<RoleChanged> roleChange = expectMatching(follower1NotifierActor, RoleChanged.class, 3);
-        assertEquals("Role change 1", RaftState.Candidate.name(), roleChange.get(0).newRole());
-        assertEquals("Role change 2", RaftState.PreLeader.name(), roleChange.get(1).newRole());
-        assertEquals("Role change 3", RaftState.Leader.name(), roleChange.get(2).newRole());
+        assertEquals("Role change 1", RaftState.Candidate, roleChange.get(0).newRole());
+        assertEquals("Role change 2", RaftState.PreLeader, roleChange.get(1).newRole());
+        assertEquals("Role change 3", RaftState.Leader, roleChange.get(2).newRole());
 
         final long previousTerm = currentTerm;
         currentTerm = follower1Context.currentTerm();
index ebbe28b11f5b198449d6e01b23fa715942500abd..897e2c0e6e22218dcde8efc1bf28070092845667 100644 (file)
@@ -425,19 +425,19 @@ public class RaftActorTest extends AbstractActorTest {
         RoleChanged raftRoleChanged = matches.get(0);
         assertEquals(persistenceId, raftRoleChanged.memberId());
         assertNull(raftRoleChanged.oldRole());
-        assertEquals(RaftState.Follower.name(), raftRoleChanged.newRole());
+        assertEquals(RaftState.Follower, raftRoleChanged.newRole());
 
         // check if the notifier got a role change from Follower to Candidate
         raftRoleChanged = matches.get(1);
         assertEquals(persistenceId, raftRoleChanged.memberId());
-        assertEquals(RaftState.Follower.name(), raftRoleChanged.oldRole());
-        assertEquals(RaftState.Candidate.name(), raftRoleChanged.newRole());
+        assertEquals(RaftState.Follower, raftRoleChanged.oldRole());
+        assertEquals(RaftState.Candidate, raftRoleChanged.newRole());
 
         // check if the notifier got a role change from Candidate to Leader
         raftRoleChanged = matches.get(2);
         assertEquals(persistenceId, raftRoleChanged.memberId());
-        assertEquals(RaftState.Candidate.name(), raftRoleChanged.oldRole());
-        assertEquals(RaftState.Leader.name(), raftRoleChanged.newRole());
+        assertEquals(RaftState.Candidate, raftRoleChanged.oldRole());
+        assertEquals(RaftState.Leader, raftRoleChanged.newRole());
 
         LeaderStateChanged leaderStateChange = MessageCollectorActor.expectFirstMatching(
                 notifierActor, LeaderStateChanged.class);
@@ -466,8 +466,8 @@ public class RaftActorTest extends AbstractActorTest {
         assertNull(leaderStateChange.leaderId());
 
         raftRoleChanged = MessageCollectorActor.expectFirstMatching(notifierActor, RoleChanged.class);
-        assertEquals(RaftState.Leader.name(), raftRoleChanged.oldRole());
-        assertEquals(RaftState.Follower.name(), raftRoleChanged.newRole());
+        assertEquals(RaftState.Leader, raftRoleChanged.oldRole());
+        assertEquals(RaftState.Follower, raftRoleChanged.newRole());
 
         MessageCollectorActor.clearMessages(notifierActor);
 
@@ -519,13 +519,13 @@ public class RaftActorTest extends AbstractActorTest {
         RoleChanged raftRoleChanged = matches.get(0);
         assertEquals(persistenceId, raftRoleChanged.memberId());
         assertNull(raftRoleChanged.oldRole());
-        assertEquals(RaftState.Follower.name(), raftRoleChanged.newRole());
+        assertEquals(RaftState.Follower, raftRoleChanged.newRole());
 
         // check if the notifier got a role change from Follower to Candidate
         raftRoleChanged = matches.get(1);
         assertEquals(persistenceId, raftRoleChanged.memberId());
-        assertEquals(RaftState.Follower.name(), raftRoleChanged.oldRole());
-        assertEquals(RaftState.Candidate.name(), raftRoleChanged.newRole());
+        assertEquals(RaftState.Follower, raftRoleChanged.oldRole());
+        assertEquals(RaftState.Candidate, raftRoleChanged.newRole());
     }
 
     @Test
index 2598e11e6936a9c883d1d9cbc4eb35e1dd2a3e14..21101d8d8c92e85afbe1ebc7d5ed3affea22d70f 100644 (file)
@@ -633,18 +633,17 @@ class ShardManager extends AbstractUntypedPersistentActorWithMetering {
     }
 
     private void onRoleChangeNotification(final RoleChangeNotification roleChanged) {
-        LOG.info("{}: Received role changed for {} from {} to {}", logName(), roleChanged.getMemberId(),
-                roleChanged.getOldRole(), roleChanged.getNewRole());
+        LOG.info("{}: Received role changed for {} from {} to {}", logName(), roleChanged.memberId(),
+            roleChanged.oldRole(), roleChanged.newRole());
 
-        ShardInformation shardInformation = findShardInformation(roleChanged.getMemberId());
+        ShardInformation shardInformation = findShardInformation(roleChanged.memberId());
         if (shardInformation != null) {
-            shardInformation.setRole(roleChanged.getNewRole());
+            shardInformation.setRole(roleChanged.newRole());
             checkReady();
             shardManagerMBean.setSyncStatus(isInSync());
         }
     }
 
-
     private ShardInformation findShardInformation(final String memberId) {
         for (ShardInformation info : localShards.values()) {
             if (info.getShardId().toString().equals(memberId)) {
index 309741b625e92703f6c6cfa862474007f1c898ec..f9a60461ab1bfaba933d504287f6b6b3b8448539 100644 (file)
@@ -63,8 +63,7 @@ public class RoleChangeNotifierTest extends AbstractActorTest {
         TestActorRef<RoleChangeNotifier> notifierTestActorRef = TestActorRef.create(getSystem(),
             RoleChangeNotifier.getProps(memberId), memberId);
 
-        notifierTestActorRef.tell(
-            new RoleChanged(memberId, RaftState.Candidate.name(), RaftState.Leader.name()), shardActor);
+        notifierTestActorRef.tell(new RoleChanged(memberId, RaftState.Leader, RaftState.Candidate), shardActor);
 
         // no notification should be sent as listener has not yet
         // registered
@@ -81,8 +80,8 @@ public class RoleChangeNotifierTest extends AbstractActorTest {
         RoleChangeNotification notification = MessageCollectorActor.getFirstMatching(listenerActor,
             RoleChangeNotification.class);
         assertNotNull(notification);
-        assertEquals(RaftState.Candidate.name(), notification.getOldRole());
-        assertEquals(RaftState.Leader.name(), notification.getNewRole());
+        assertEquals(RaftState.Candidate.name(), notification.oldRole());
+        assertEquals(RaftState.Leader.name(), notification.newRole());
     }
 
     @Test