Ayaka Koshibe
Committed by Ray Milkey

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
......@@ -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 @@ public class Leadership {
}
/**
* 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 @@ public class Leadership {
@Override
public int hashCode() {
return Objects.hash(topic, leader, epoch);
return Objects.hash(topic, leader, candidates, epoch);
}
@Override
......@@ -95,6 +128,7 @@ public class Leadership {
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 @@ public class Leadership {
return MoreObjects.toStringHelper(this.getClass())
.add("topic", topic)
.add("leader", leader)
.add("candidates", candidates)
.add("epoch", epoch)
.add("electedTime", new DateTime(electedTime))
.toString();
......
......@@ -46,7 +46,14 @@ public class LeadershipEvent extends AbstractEvent<LeadershipEvent.Type, Leaders
* 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
}
/**
......
......@@ -17,6 +17,7 @@ package org.onosproject.cluster;
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 @@ import com.google.common.collect.ImmutableList;
* 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 @@ public class RoleInfo {
@Override
public String toString() {
return MoreObjects.toStringHelper(this.getClass())
.add("master", master)
.add("master", master.orElseGet(() -> null))
.add("backups", backups)
.toString();
}
......
......@@ -20,9 +20,9 @@ import java.util.List;
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 @@ public class RoleInfoTest {
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);
......