Leadership construct includes List of NodeIds to describe current
leader/backups (candidates) for a topic. This includes removing the RoleInfo in
LeadershipEvent, to deduplicate information.
RoleInfo is also made a bit saner with the Optional leader field.
part of: Device Mastership store on top of LeadershipService
Reference: ONOS-76
Change-Id: I957c4d79125873d5a7280f60231d26d2806ab27f
diff --git a/core/api/src/main/java/org/onosproject/cluster/Leadership.java b/core/api/src/main/java/org/onosproject/cluster/Leadership.java
index 5f3a9ef..d063a19 100644
--- a/core/api/src/main/java/org/onosproject/cluster/Leadership.java
+++ b/core/api/src/main/java/org/onosproject/cluster/Leadership.java
@@ -16,24 +16,46 @@
package org.onosproject.cluster;
import java.util.Objects;
+import java.util.List;
import org.joda.time.DateTime;
import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
/**
- * Abstract leadership concept.
+ * Abstract leadership concept. The information carried by this construct
+ * include the topic of contention, the {@link NodeId}s of Nodes that could
+ * become leader for the topic, the epoch when the term for a given leader
+ * began, and the system time when the term began. Note:
+ * <ul>
+ * <li>The list of NodeIds may include the current leader at index 0, and the
+ * rest in decreasing preference order.</li>
+ * <li>The epoch is the logical age of a Leadership construct, and should be
+ * used for comparing two Leaderships, but only of the same topic.</li>
+ * </ul>
*/
public class Leadership {
private final String topic;
private final NodeId leader;
+ private final List<NodeId> candidates;
private final long epoch;
private final long electedTime;
public Leadership(String topic, NodeId leader, long epoch, long electedTime) {
this.topic = topic;
this.leader = leader;
+ this.candidates = ImmutableList.of(leader);
+ this.epoch = epoch;
+ this.electedTime = electedTime;
+ }
+
+ public Leadership(String topic, NodeId leader, List<NodeId> candidates,
+ long epoch, long electedTime) {
+ this.topic = topic;
+ this.leader = leader;
+ this.candidates = ImmutableList.copyOf(candidates);
this.epoch = epoch;
this.electedTime = electedTime;
}
@@ -55,12 +77,23 @@
}
/**
+ * Returns an preference-ordered list of nodes that are in the leadership
+ * race for this topic.
+ *
+ * @return a list of NodeIds in priority-order, or an empty list.
+ */
+ public List<NodeId> candidates() {
+ return candidates;
+ }
+
+ /**
* The epoch when the leadership was assumed.
* <p>
- * Comparing epochs is only appropriate for leadership
- * events for the same topic. The system guarantees that
- * for any given topic the epoch for a new term is higher
- * (not necessarily by 1) than the epoch for any previous term.
+ * Comparing epochs is only appropriate for leadership events for the same
+ * topic. The system guarantees that for any given topic the epoch for a new
+ * term is higher (not necessarily by 1) than the epoch for any previous
+ * term.
+ *
* @return leadership epoch
*/
public long epoch() {
@@ -83,7 +116,7 @@
@Override
public int hashCode() {
- return Objects.hash(topic, leader, epoch);
+ return Objects.hash(topic, leader, candidates, epoch);
}
@Override
@@ -95,6 +128,7 @@
final Leadership other = (Leadership) obj;
return Objects.equals(this.topic, other.topic) &&
Objects.equals(this.leader, other.leader) &&
+ Objects.equals(this.candidates, other.candidates) &&
Objects.equals(this.epoch, other.epoch) &&
Objects.equals(this.electedTime, other.electedTime);
}
@@ -106,6 +140,7 @@
return MoreObjects.toStringHelper(this.getClass())
.add("topic", topic)
.add("leader", leader)
+ .add("candidates", candidates)
.add("epoch", epoch)
.add("electedTime", new DateTime(electedTime))
.toString();
diff --git a/core/api/src/main/java/org/onosproject/cluster/LeadershipEvent.java b/core/api/src/main/java/org/onosproject/cluster/LeadershipEvent.java
index 4eaf686..ac0036e 100644
--- a/core/api/src/main/java/org/onosproject/cluster/LeadershipEvent.java
+++ b/core/api/src/main/java/org/onosproject/cluster/LeadershipEvent.java
@@ -46,7 +46,14 @@
* Signifies that the leader has been booted and lost leadership. The event subject is the
* former leader.
*/
- LEADER_BOOTED
+ LEADER_BOOTED,
+
+ /**
+ * Signifies that the list of candidates for leadership for a resource
+ * has changed. If the change in the backups list is accompanied by a
+ * change in the leader, the event is subsumed by the leadership change.
+ */
+ LEADER_CANDIDATES_CHANGED
}
/**
diff --git a/core/api/src/main/java/org/onosproject/cluster/RoleInfo.java b/core/api/src/main/java/org/onosproject/cluster/RoleInfo.java
index a02cda9..f7ffc9b 100644
--- a/core/api/src/main/java/org/onosproject/cluster/RoleInfo.java
+++ b/core/api/src/main/java/org/onosproject/cluster/RoleInfo.java
@@ -17,6 +17,7 @@
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
@@ -27,21 +28,22 @@
* master and a preference-ordered list of backup nodes.
*/
public class RoleInfo {
- private final NodeId master;
+ private final Optional<NodeId> master;
private final List<NodeId> backups;
public RoleInfo(NodeId master, List<NodeId> backups) {
- this.master = master;
+ this.master = Optional.ofNullable(master);
this.backups = ImmutableList.copyOf(backups);
}
public RoleInfo() {
- this.master = null;
+ this.master = Optional.empty();
this.backups = ImmutableList.of();
}
+ // This will return a Optional<NodeId> in the future.
public NodeId master() {
- return master;
+ return master.orElseGet(() -> null);
}
public List<NodeId> backups() {
@@ -74,7 +76,7 @@
@Override
public String toString() {
return MoreObjects.toStringHelper(this.getClass())
- .add("master", master)
+ .add("master", master.orElseGet(() -> null))
.add("backups", backups)
.toString();
}
diff --git a/core/api/src/test/java/org/onosproject/cluster/RoleInfoTest.java b/core/api/src/test/java/org/onosproject/cluster/RoleInfoTest.java
index 172b6f5..6a4f968 100644
--- a/core/api/src/test/java/org/onosproject/cluster/RoleInfoTest.java
+++ b/core/api/src/test/java/org/onosproject/cluster/RoleInfoTest.java
@@ -20,9 +20,9 @@
import org.junit.Test;
import com.google.common.collect.Lists;
+import com.google.common.testing.EqualsTester;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
/**
* Test to check behavioral correctness of the RoleInfo structure.
@@ -39,14 +39,20 @@
private static final RoleInfo RI1 = new RoleInfo(N1, BKUP1);
private static final RoleInfo RI2 = new RoleInfo(N1, BKUP2);
private static final RoleInfo RI3 = new RoleInfo(N2, BKUP1);
+ private static final RoleInfo RI4 = new RoleInfo(null, BKUP2);
+
+ @Test
+ public void testEquality() {
+ new EqualsTester()
+ .addEqualityGroup(RI1, new RoleInfo(new NodeId("n1"), Lists.newArrayList(N2, N3)))
+ .addEqualityGroup(RI3);
+ }
@Test
public void basics() {
assertEquals("wrong master", new NodeId("n1"), RI1.master());
assertEquals("wrong Backups", RI1.backups(), Lists.newArrayList(N2, N3));
-
- assertNotEquals("equals() broken", RI1, RI2);
- assertNotEquals("equals() broken", RI1, RI3);
+ assertEquals("wrong empty master", RI4.master(), null);
List<NodeId> bkup3 = Lists.newArrayList(N3, new NodeId("n4"));
assertEquals("equals() broken", new RoleInfo(N1, bkup3), RI2);