Madan Jampani

LeadershipStore updates:

 - Now tracking leader and candidates for a topic using a single map.
 - Using term numbers that are incremented by one every time a new leader is elected.
 - Introduced a separate LeadershipStore to conform to the  manager-store pattern

Change-Id: I1d03a6c5e8ff0e68ef0c1e3a6c2d425c4856e470
Showing 23 changed files with 897 additions and 1021 deletions
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
16 package org.onosproject.cip; 16 package org.onosproject.cip;
17 17
18 import com.google.common.io.ByteStreams; 18 import com.google.common.io.ByteStreams;
19 +
19 import org.apache.felix.scr.annotations.Activate; 20 import org.apache.felix.scr.annotations.Activate;
20 import org.apache.felix.scr.annotations.Component; 21 import org.apache.felix.scr.annotations.Component;
21 import org.apache.felix.scr.annotations.Deactivate; 22 import org.apache.felix.scr.annotations.Deactivate;
...@@ -93,7 +94,7 @@ public class ClusterIpManager { ...@@ -93,7 +94,7 @@ public class ClusterIpManager {
93 cfgService.registerProperties(getClass()); 94 cfgService.registerProperties(getClass());
94 95
95 localId = clusterService.getLocalNode().id(); 96 localId = clusterService.getLocalNode().id();
96 - processLeadershipChange(leadershipService.getLeader(CLUSTER_IP)); 97 + processLeaderChange(leadershipService.getLeader(CLUSTER_IP));
97 98
98 leadershipService.addListener(listener); 99 leadershipService.addListener(listener);
99 leadershipService.runForLeadership(CLUSTER_IP); 100 leadershipService.runForLeadership(CLUSTER_IP);
...@@ -137,10 +138,7 @@ public class ClusterIpManager { ...@@ -137,10 +138,7 @@ public class ClusterIpManager {
137 } 138 }
138 } 139 }
139 140
140 - private synchronized void processLeadershipChange(NodeId newLeader) { 141 + private synchronized void processLeaderChange(NodeId newLeader) {
141 - if (newLeader == null) {
142 - return;
143 - }
144 boolean isLeader = Objects.equals(newLeader, localId); 142 boolean isLeader = Objects.equals(newLeader, localId);
145 log.info("Processing leadership change; wasLeader={}, isLeader={}", wasLeader, isLeader); 143 log.info("Processing leadership change; wasLeader={}, isLeader={}", wasLeader, isLeader);
146 if (!wasLeader && isLeader) { 144 if (!wasLeader && isLeader) {
...@@ -189,11 +187,15 @@ public class ClusterIpManager { ...@@ -189,11 +187,15 @@ public class ClusterIpManager {
189 187
190 // Listens for leadership changes. 188 // Listens for leadership changes.
191 private class InternalLeadershipListener implements LeadershipEventListener { 189 private class InternalLeadershipListener implements LeadershipEventListener {
190 +
192 @Override 191 @Override
193 - public void event(LeadershipEvent event) { 192 + public boolean isRelevant(LeadershipEvent event) {
194 - if (event.subject().topic().equals(CLUSTER_IP)) { 193 + return CLUSTER_IP.equals(event.subject().topic());
195 - processLeadershipChange(event.subject().leader());
196 } 194 }
195 +
196 + @Override
197 + public void event(LeadershipEvent event) {
198 + processLeaderChange(event.subject().leaderNodeId());
197 } 199 }
198 } 200 }
199 201
......
...@@ -19,6 +19,7 @@ package org.onosproject.mlb; ...@@ -19,6 +19,7 @@ package org.onosproject.mlb;
19 import com.google.common.util.concurrent.ListenableScheduledFuture; 19 import com.google.common.util.concurrent.ListenableScheduledFuture;
20 import com.google.common.util.concurrent.ListeningScheduledExecutorService; 20 import com.google.common.util.concurrent.ListeningScheduledExecutorService;
21 import com.google.common.util.concurrent.MoreExecutors; 21 import com.google.common.util.concurrent.MoreExecutors;
22 +
22 import org.apache.felix.scr.annotations.Activate; 23 import org.apache.felix.scr.annotations.Activate;
23 import org.apache.felix.scr.annotations.Component; 24 import org.apache.felix.scr.annotations.Component;
24 import org.apache.felix.scr.annotations.Deactivate; 25 import org.apache.felix.scr.annotations.Deactivate;
...@@ -105,10 +106,7 @@ public class MastershipLoadBalancer { ...@@ -105,10 +106,7 @@ public class MastershipLoadBalancer {
105 log.info("Stopped"); 106 log.info("Stopped");
106 } 107 }
107 108
108 - private synchronized void processLeadershipChange(NodeId newLeader) { 109 + private synchronized void processLeaderChange(NodeId newLeader) {
109 - if (newLeader == null) {
110 - return;
111 - }
112 boolean currLeader = newLeader.equals(localId); 110 boolean currLeader = newLeader.equals(localId);
113 if (isLeader.getAndSet(currLeader) != currLeader) { 111 if (isLeader.getAndSet(currLeader) != currLeader) {
114 if (currLeader) { 112 if (currLeader) {
...@@ -159,7 +157,7 @@ public class MastershipLoadBalancer { ...@@ -159,7 +157,7 @@ public class MastershipLoadBalancer {
159 157
160 @Override 158 @Override
161 public void event(LeadershipEvent event) { 159 public void event(LeadershipEvent event) {
162 - processLeadershipChange(event.subject().leader()); 160 + processLeaderChange(event.subject().leaderNodeId());
163 } 161 }
164 } 162 }
165 } 163 }
......
...@@ -26,6 +26,7 @@ import org.onosproject.cluster.ClusterService; ...@@ -26,6 +26,7 @@ import org.onosproject.cluster.ClusterService;
26 import org.onosproject.cluster.LeadershipEvent; 26 import org.onosproject.cluster.LeadershipEvent;
27 import org.onosproject.cluster.LeadershipEventListener; 27 import org.onosproject.cluster.LeadershipEventListener;
28 import org.onosproject.cluster.LeadershipService; 28 import org.onosproject.cluster.LeadershipService;
29 +import org.onosproject.cluster.NodeId;
29 import org.onosproject.core.ApplicationId; 30 import org.onosproject.core.ApplicationId;
30 import org.onosproject.core.CoreService; 31 import org.onosproject.core.CoreService;
31 import org.onosproject.net.intent.Intent; 32 import org.onosproject.net.intent.Intent;
...@@ -43,7 +44,6 @@ import java.util.LinkedList; ...@@ -43,7 +44,6 @@ import java.util.LinkedList;
43 import java.util.List; 44 import java.util.List;
44 import java.util.Map; 45 import java.util.Map;
45 import java.util.Map.Entry; 46 import java.util.Map.Entry;
46 -import java.util.Objects;
47 import java.util.concurrent.ConcurrentHashMap; 47 import java.util.concurrent.ConcurrentHashMap;
48 import java.util.concurrent.ExecutorService; 48 import java.util.concurrent.ExecutorService;
49 49
...@@ -74,6 +74,7 @@ public class IntentSynchronizer implements IntentSynchronizationService, ...@@ -74,6 +74,7 @@ public class IntentSynchronizer implements IntentSynchronizationService,
74 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 74 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
75 protected IntentService intentService; 75 protected IntentService intentService;
76 76
77 + private NodeId localNodeId;
77 private ApplicationId appId; 78 private ApplicationId appId;
78 79
79 private final InternalLeadershipListener leadershipEventListener = 80 private final InternalLeadershipListener leadershipEventListener =
...@@ -89,7 +90,7 @@ public class IntentSynchronizer implements IntentSynchronizationService, ...@@ -89,7 +90,7 @@ public class IntentSynchronizer implements IntentSynchronizationService,
89 @Activate 90 @Activate
90 public void activate() { 91 public void activate() {
91 intentsSynchronizerExecutor = createExecutor(); 92 intentsSynchronizerExecutor = createExecutor();
92 - 93 + this.localNodeId = clusterService.getLocalNode().id();
93 this.appId = coreService.registerApplication(APP_NAME); 94 this.appId = coreService.registerApplication(APP_NAME);
94 95
95 leadershipService.addListener(leadershipEventListener); 96 leadershipService.addListener(leadershipEventListener);
...@@ -268,27 +269,22 @@ public class IntentSynchronizer implements IntentSynchronizationService, ...@@ -268,27 +269,22 @@ public class IntentSynchronizer implements IntentSynchronizationService,
268 private class InternalLeadershipListener implements LeadershipEventListener { 269 private class InternalLeadershipListener implements LeadershipEventListener {
269 270
270 @Override 271 @Override
271 - public void event(LeadershipEvent event) { 272 + public boolean isRelevant(LeadershipEvent event) {
272 - if (!event.subject().topic().equals(appId.name())) { 273 + return event.subject().topic().equals(appId.name());
273 - // Not our topic: ignore
274 - return;
275 - }
276 - if (!Objects.equals(event.subject().leader(),
277 - clusterService.getLocalNode().id())) {
278 - // The event is not about this instance: ignore
279 - return;
280 } 274 }
281 275
276 + @Override
277 + public void event(LeadershipEvent event) {
282 switch (event.type()) { 278 switch (event.type()) {
283 - case LEADER_ELECTED: 279 + case LEADER_CHANGED:
280 + case LEADER_AND_CANDIDATES_CHANGED:
281 + if (localNodeId.equals(event.subject().leaderNodeId())) {
284 log.info("IntentSynchronizer gained leadership"); 282 log.info("IntentSynchronizer gained leadership");
285 leaderChanged(true); 283 leaderChanged(true);
286 - break; 284 + } else {
287 - case LEADER_BOOTED: 285 + log.info("IntentSynchronizer leader changed. New leader is {}", event.subject().leaderNodeId());
288 - log.info("IntentSynchronizer lost leadership");
289 leaderChanged(false); 286 leaderChanged(false);
290 - break; 287 + }
291 - case LEADER_REELECTED:
292 default: 288 default:
293 break; 289 break;
294 } 290 }
......
...@@ -17,6 +17,7 @@ package org.onosproject.routing.impl; ...@@ -17,6 +17,7 @@ package org.onosproject.routing.impl;
17 17
18 import com.google.common.collect.Sets; 18 import com.google.common.collect.Sets;
19 import com.google.common.util.concurrent.MoreExecutors; 19 import com.google.common.util.concurrent.MoreExecutors;
20 +
20 import org.junit.Before; 21 import org.junit.Before;
21 import org.junit.Test; 22 import org.junit.Test;
22 import org.onlab.junit.TestUtils; 23 import org.onlab.junit.TestUtils;
...@@ -29,7 +30,11 @@ import org.onlab.packet.IpPrefix; ...@@ -29,7 +30,11 @@ import org.onlab.packet.IpPrefix;
29 import org.onlab.packet.MacAddress; 30 import org.onlab.packet.MacAddress;
30 import org.onlab.packet.VlanId; 31 import org.onlab.packet.VlanId;
31 import org.onosproject.TestApplicationId; 32 import org.onosproject.TestApplicationId;
33 +import org.onosproject.cluster.ClusterServiceAdapter;
34 +import org.onosproject.cluster.ControllerNode;
35 +import org.onosproject.cluster.DefaultControllerNode;
32 import org.onosproject.cluster.LeadershipServiceAdapter; 36 import org.onosproject.cluster.LeadershipServiceAdapter;
37 +import org.onosproject.cluster.NodeId;
33 import org.onosproject.core.ApplicationId; 38 import org.onosproject.core.ApplicationId;
34 import org.onosproject.core.CoreServiceAdapter; 39 import org.onosproject.core.CoreServiceAdapter;
35 import org.onosproject.incubator.net.intf.Interface; 40 import org.onosproject.incubator.net.intf.Interface;
...@@ -94,6 +99,9 @@ public class IntentSynchronizerTest extends AbstractIntentTest { ...@@ -94,6 +99,9 @@ public class IntentSynchronizerTest extends AbstractIntentTest {
94 private static final ApplicationId APPID = 99 private static final ApplicationId APPID =
95 TestApplicationId.create("intent-sync-test"); 100 TestApplicationId.create("intent-sync-test");
96 101
102 + private static final ControllerNode LOCAL_NODE =
103 + new DefaultControllerNode(new NodeId("foo"), IpAddress.valueOf("127.0.0.1"));
104 +
97 @Before 105 @Before
98 public void setUp() throws Exception { 106 public void setUp() throws Exception {
99 super.setUp(); 107 super.setUp();
...@@ -105,6 +113,7 @@ public class IntentSynchronizerTest extends AbstractIntentTest { ...@@ -105,6 +113,7 @@ public class IntentSynchronizerTest extends AbstractIntentTest {
105 intentSynchronizer = new TestIntentSynchronizer(); 113 intentSynchronizer = new TestIntentSynchronizer();
106 114
107 intentSynchronizer.coreService = new TestCoreService(); 115 intentSynchronizer.coreService = new TestCoreService();
116 + intentSynchronizer.clusterService = new TestClusterService();
108 intentSynchronizer.leadershipService = new TestLeadershipService(); 117 intentSynchronizer.leadershipService = new TestLeadershipService();
109 intentSynchronizer.intentService = intentService; 118 intentSynchronizer.intentService = intentService;
110 119
...@@ -441,6 +450,13 @@ public class IntentSynchronizerTest extends AbstractIntentTest { ...@@ -441,6 +450,13 @@ public class IntentSynchronizerTest extends AbstractIntentTest {
441 } 450 }
442 } 451 }
443 452
453 + private class TestClusterService extends ClusterServiceAdapter {
454 + @Override
455 + public ControllerNode getLocalNode() {
456 + return LOCAL_NODE;
457 + }
458 + }
459 +
444 private class TestLeadershipService extends LeadershipServiceAdapter { 460 private class TestLeadershipService extends LeadershipServiceAdapter {
445 461
446 } 462 }
......
...@@ -24,12 +24,11 @@ import org.apache.felix.scr.annotations.Reference; ...@@ -24,12 +24,11 @@ import org.apache.felix.scr.annotations.Reference;
24 import org.apache.felix.scr.annotations.ReferenceCardinality; 24 import org.apache.felix.scr.annotations.ReferenceCardinality;
25 import org.onosproject.cluster.ClusterService; 25 import org.onosproject.cluster.ClusterService;
26 import org.onosproject.core.CoreService; 26 import org.onosproject.core.CoreService;
27 -import org.onosproject.cluster.ControllerNode;
28 import org.onosproject.cluster.LeadershipEvent; 27 import org.onosproject.cluster.LeadershipEvent;
29 import org.onosproject.cluster.LeadershipEventListener; 28 import org.onosproject.cluster.LeadershipEventListener;
30 import org.onosproject.cluster.LeadershipService; 29 import org.onosproject.cluster.LeadershipService;
30 +import org.onosproject.cluster.NodeId;
31 import org.onosproject.core.ApplicationId; 31 import org.onosproject.core.ApplicationId;
32 -
33 import org.slf4j.Logger; 32 import org.slf4j.Logger;
34 33
35 34
...@@ -56,7 +55,7 @@ public class ElectionTest { ...@@ -56,7 +55,7 @@ public class ElectionTest {
56 private LeadershipEventListener leadershipEventListener = 55 private LeadershipEventListener leadershipEventListener =
57 new InnerLeadershipEventListener(); 56 new InnerLeadershipEventListener();
58 57
59 - private ControllerNode localControllerNode; 58 + private NodeId localNodeId;
60 59
61 60
62 @Activate 61 @Activate
...@@ -65,7 +64,7 @@ public class ElectionTest { ...@@ -65,7 +64,7 @@ public class ElectionTest {
65 64
66 appId = coreService.registerApplication(ELECTION_APP); 65 appId = coreService.registerApplication(ELECTION_APP);
67 66
68 - localControllerNode = clusterService.getLocalNode(); 67 + localNodeId = clusterService.getLocalNode().id();
69 68
70 leadershipService.addListener(leadershipEventListener); 69 leadershipService.addListener(leadershipEventListener);
71 } 70 }
...@@ -100,20 +99,10 @@ public class ElectionTest { ...@@ -100,20 +99,10 @@ public class ElectionTest {
100 log.debug("Leadership Event: time = {} type = {} event = {}", 99 log.debug("Leadership Event: time = {} type = {} event = {}",
101 event.time(), event.type(), event); 100 event.time(), event.type(), event);
102 101
103 - if (!event.subject().leader().equals(
104 - localControllerNode.id())) {
105 - return; // The event is not about this instance: ignore
106 - }
107 -
108 switch (event.type()) { 102 switch (event.type()) {
109 - case LEADER_ELECTED: 103 + case LEADER_CHANGED:
110 - log.info("Election-test app leader elected"); 104 + case LEADER_AND_CANDIDATES_CHANGED:
111 - break; 105 + log.info("Election-test app leader changed. New leadership: {}", event.subject());
112 - case LEADER_BOOTED:
113 - log.info("Election-test app lost election");
114 - break;
115 - case LEADER_REELECTED:
116 - log.debug("Election-test app was re-elected");
117 break; 106 break;
118 default: 107 default:
119 break; 108 break;
......
...@@ -40,7 +40,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; ...@@ -40,7 +40,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
40 description = "Finds the leader for particular topic.") 40 description = "Finds the leader for particular topic.")
41 public class LeaderCommand extends AbstractShellCommand { 41 public class LeaderCommand extends AbstractShellCommand {
42 42
43 - private static final String FMT = "%-30s | %-15s | %-6s | %-10s |"; 43 + private static final String FMT = "%-30s | %-15s | %-5s | %-10s |";
44 private static final String FMT_C = "%-30s | %-15s | %-19s |"; 44 private static final String FMT_C = "%-30s | %-15s | %-19s |";
45 private boolean allTopics; 45 private boolean allTopics;
46 private Pattern pattern; 46 private Pattern pattern;
...@@ -57,19 +57,8 @@ public class LeaderCommand extends AbstractShellCommand { ...@@ -57,19 +57,8 @@ public class LeaderCommand extends AbstractShellCommand {
57 /** 57 /**
58 * Compares leaders, sorting by toString() output. 58 * Compares leaders, sorting by toString() output.
59 */ 59 */
60 - private Comparator<Leadership> leadershipComparator = 60 + private Comparator<Leadership> leadershipComparator = (l1, l2) ->
61 - (e1, e2) -> { 61 + String.valueOf(l1.leaderNodeId()).compareTo(String.valueOf(l2.leaderNodeId()));
62 - if (e1.leader() == null && e2.leader() == null) {
63 - return 0;
64 - }
65 - if (e1.leader() == null) {
66 - return 1;
67 - }
68 - if (e2.leader() == null) {
69 - return -1;
70 - }
71 - return e1.leader().toString().compareTo(e2.leader().toString());
72 - };
73 62
74 /** 63 /**
75 * Displays text representing the leaders. 64 * Displays text representing the leaders.
...@@ -78,18 +67,19 @@ public class LeaderCommand extends AbstractShellCommand { ...@@ -78,18 +67,19 @@ public class LeaderCommand extends AbstractShellCommand {
78 */ 67 */
79 private void displayLeaders(Map<String, Leadership> leaderBoard) { 68 private void displayLeaders(Map<String, Leadership> leaderBoard) {
80 print("------------------------------------------------------------------------"); 69 print("------------------------------------------------------------------------");
81 - print(FMT, "Topic", "Leader", "Epoch", "Elected"); 70 + print(FMT, "Topic", "Leader", "Term", "Elected");
82 print("------------------------------------------------------------------------"); 71 print("------------------------------------------------------------------------");
83 72
84 leaderBoard.values() 73 leaderBoard.values()
85 .stream() 74 .stream()
86 .filter(l -> allTopics || pattern.matcher(l.topic()).matches()) 75 .filter(l -> allTopics || pattern.matcher(l.topic()).matches())
76 + .filter(l -> l.leader() != null)
87 .sorted(leadershipComparator) 77 .sorted(leadershipComparator)
88 .forEach(l -> print(FMT, 78 .forEach(l -> print(FMT,
89 l.topic(), 79 l.topic(),
90 - l.leader(), 80 + l.leaderNodeId(),
91 - l.epoch(), 81 + l.leader().term(),
92 - Tools.timeAgo(l.electedTime()))); 82 + Tools.timeAgo(l.leader().termStartTime())));
93 print("------------------------------------------------------------------------"); 83 print("------------------------------------------------------------------------");
94 } 84 }
95 85
...@@ -110,7 +100,7 @@ public class LeaderCommand extends AbstractShellCommand { ...@@ -110,7 +100,7 @@ public class LeaderCommand extends AbstractShellCommand {
110 Leadership l = leaderBoard.get(es.getKey()); 100 Leadership l = leaderBoard.get(es.getKey());
111 print(FMT_C, 101 print(FMT_C,
112 es.getKey(), 102 es.getKey(),
113 - l == null ? "null" : l.leader(), 103 + String.valueOf(l.leaderNodeId()),
114 // formatting hacks to get it into a table 104 // formatting hacks to get it into a table
115 list.get(0).toString()); 105 list.get(0).toString());
116 list.subList(1, list.size()).forEach(n -> print(FMT_C, " ", " ", n)); 106 list.subList(1, list.size()).forEach(n -> print(FMT_C, " ", " ", n));
...@@ -134,10 +124,10 @@ public class LeaderCommand extends AbstractShellCommand { ...@@ -134,10 +124,10 @@ public class LeaderCommand extends AbstractShellCommand {
134 result.add( 124 result.add(
135 mapper.createObjectNode() 125 mapper.createObjectNode()
136 .put("topic", l.topic()) 126 .put("topic", l.topic())
137 - .put("leader", l.leader().toString()) 127 + .put("leader", String.valueOf(l.leaderNodeId()))
138 .put("candidates", l.candidates().toString()) 128 .put("candidates", l.candidates().toString())
139 - .put("epoch", l.epoch()) 129 + .put("epoch", l.leader().term())
140 - .put("electedTime", Tools.timeAgo(l.electedTime())))); 130 + .put("epochStartTime", Tools.timeAgo(l.leader().termStartTime()))));
141 131
142 return result; 132 return result;
143 } 133 }
......
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.cluster;
17 +
18 +import com.google.common.base.MoreObjects;
19 +import com.google.common.base.Objects;
20 +import static com.google.common.base.Preconditions.checkNotNull;
21 +import static com.google.common.base.Preconditions.checkArgument;
22 +
23 +/**
24 + * Topic leader.
25 + * <p>
26 + * Identified by the {@link NodeId node identifier} and a monotonically increasing term number.
27 + * The term number is incremented by one every time a new node is elected as leader.
28 + * Also available is the system clock time at the instant when this node was elected as leader.
29 + * Keep in mind though that as with any system clock based time stamps this particular information
30 + * susceptible to clock skew and should only be relied on for simple diagnostic purposes.
31 + */
32 +public class Leader {
33 + private final NodeId nodeId;
34 + private final long term;
35 + private final long termStartTime;
36 +
37 + public Leader(NodeId nodeId, long term, long termStartTime) {
38 + this.nodeId = checkNotNull(nodeId);
39 + checkArgument(term >= 0, "term must be non-negative");
40 + this.term = term;
41 + checkArgument(termStartTime >= 0, "termStartTime must be non-negative");
42 + this.termStartTime = termStartTime;
43 + }
44 +
45 + /**
46 + * Returns the identifier for of leader.
47 + * @return node identifier
48 + */
49 + public NodeId nodeId() {
50 + return nodeId;
51 + }
52 +
53 + /**
54 + * Returns the leader's term.
55 + * @return leader term
56 + */
57 + public long term() {
58 + return term;
59 + }
60 +
61 + /**
62 + * Returns the system time when the current leadership term started.
63 + * @return current leader term start time
64 + */
65 + public long termStartTime() {
66 + return termStartTime;
67 + }
68 +
69 + @Override
70 + public boolean equals(Object other) {
71 + if (this == other) {
72 + return true;
73 + }
74 + if (other != null && other instanceof Leader) {
75 + Leader that = (Leader) other;
76 + return Objects.equal(this.nodeId, that.nodeId) &&
77 + this.term == that.term &&
78 + this.termStartTime == that.termStartTime;
79 + }
80 + return false;
81 + }
82 +
83 + @Override
84 + public int hashCode() {
85 + return Objects.hashCode(nodeId, term, termStartTime);
86 + }
87 +
88 + @Override
89 + public String toString() {
90 + return MoreObjects.toStringHelper(getClass())
91 + .add("nodeId", nodeId)
92 + .add("term", term)
93 + .add("termStartTime", termStartTime)
94 + .toString();
95 + }
96 +}
...@@ -17,63 +17,31 @@ package org.onosproject.cluster; ...@@ -17,63 +17,31 @@ package org.onosproject.cluster;
17 17
18 import java.util.Objects; 18 import java.util.Objects;
19 import java.util.List; 19 import java.util.List;
20 -import java.util.Optional;
21 -
22 -import org.joda.time.DateTime;
23 20
24 import com.google.common.base.MoreObjects; 21 import com.google.common.base.MoreObjects;
25 import com.google.common.collect.ImmutableList; 22 import com.google.common.collect.ImmutableList;
26 23
27 /** 24 /**
28 - * Abstract leadership concept. The information carried by this construct 25 + * State of leadership for topic.
29 - * include the topic of contention, the {@link NodeId}s of Nodes that could 26 + * <p>
30 - * become leader for the topic, the epoch when the term for a given leader 27 + * Provided by this construct is the current {@link Leader leader} and the list of
31 - * began, and the system time when the term began. Note: 28 + * {@link NodeId nodeId}s currently registered as candidates for election for the topic.
32 - * <ul> 29 + * Keep in mind that only registered candidates can become leaders.
33 - * <li>The list of NodeIds may include the current leader at index 0, and the
34 - * rest in decreasing preference order.</li>
35 - * <li>The epoch is the logical age of a Leadership construct, and should be
36 - * used for comparing two Leaderships, but only of the same topic.</li>
37 - * <li>The leader may be null if its accuracy can't be guaranteed. This applies
38 - * to CANDIDATES_CHANGED events and candidate board contents.</li>
39 - * </ul>
40 */ 30 */
41 public class Leadership { 31 public class Leadership {
42 32
43 private final String topic; 33 private final String topic;
44 - private final Optional<NodeId> leader; 34 + private final Leader leader;
45 private final List<NodeId> candidates; 35 private final List<NodeId> candidates;
46 - private final long epoch;
47 - private final long electedTime;
48 36
49 - public Leadership(String topic, NodeId leader, long epoch, long electedTime) { 37 + public Leadership(String topic, Leader leader, List<NodeId> candidates) {
50 this.topic = topic; 38 this.topic = topic;
51 - this.leader = Optional.of(leader); 39 + this.leader = leader;
52 - this.candidates = ImmutableList.of(leader);
53 - this.epoch = epoch;
54 - this.electedTime = electedTime;
55 - }
56 -
57 - public Leadership(String topic, NodeId leader, List<NodeId> candidates,
58 - long epoch, long electedTime) {
59 - this.topic = topic;
60 - this.leader = Optional.of(leader);
61 this.candidates = ImmutableList.copyOf(candidates); 40 this.candidates = ImmutableList.copyOf(candidates);
62 - this.epoch = epoch;
63 - this.electedTime = electedTime;
64 - }
65 -
66 - public Leadership(String topic, List<NodeId> candidates,
67 - long epoch, long electedTime) {
68 - this.topic = topic;
69 - this.leader = Optional.empty();
70 - this.candidates = ImmutableList.copyOf(candidates);
71 - this.epoch = epoch;
72 - this.electedTime = electedTime;
73 } 41 }
74 42
75 /** 43 /**
76 - * The topic for which this leadership applies. 44 + * Returns the leadership topic.
77 * 45 *
78 * @return leadership topic. 46 * @return leadership topic.
79 */ 47 */
...@@ -82,57 +50,36 @@ public class Leadership { ...@@ -82,57 +50,36 @@ public class Leadership {
82 } 50 }
83 51
84 /** 52 /**
85 - * The nodeId of leader for this topic. 53 + * Returns the {@link NodeId nodeId} of the leader.
86 - *
87 - * @return leader node.
88 - */
89 - // This will return Optional<NodeId> in the future.
90 - public NodeId leader() {
91 - return leader.orElse(null);
92 - }
93 -
94 - /**
95 - * Returns an preference-ordered list of nodes that are in the leadership
96 - * race for this topic.
97 * 54 *
98 - * @return a list of NodeIds in priority-order, or an empty list. 55 + * @return leader node identifier; will be null if there is no leader
99 */ 56 */
100 - public List<NodeId> candidates() { 57 + public NodeId leaderNodeId() {
101 - return candidates; 58 + return leader == null ? null : leader.nodeId();
102 } 59 }
103 60
104 /** 61 /**
105 - * The epoch when the leadership was assumed. 62 + * Returns the leader for this topic.
106 - * <p>
107 - * Comparing epochs is only appropriate for leadership events for the same
108 - * topic. The system guarantees that for any given topic the epoch for a new
109 - * term is higher (not necessarily by 1) than the epoch for any previous
110 - * term.
111 * 63 *
112 - * @return leadership epoch 64 + * @return leader; will be null if there is no leader for topic
113 */ 65 */
114 - public long epoch() { 66 + public Leader leader() {
115 - return epoch; 67 + return leader;
116 } 68 }
117 69
118 /** 70 /**
119 - * The system time when the term started. 71 + * Returns an preference-ordered list of nodes that are in the leadership
120 - * <p> 72 + * race for this topic.
121 - * The elected time is initially set on the node coordinating
122 - * the leader election using its local system time. Due to possible
123 - * clock skew, relying on this value for determining event ordering
124 - * is discouraged. Epoch is more appropriate for determining
125 - * event ordering.
126 * 73 *
127 - * @return elected time. 74 + * @return a list of NodeIds in priority-order, or an empty list.
128 */ 75 */
129 - public long electedTime() { 76 + public List<NodeId> candidates() {
130 - return electedTime; 77 + return candidates;
131 } 78 }
132 79
133 @Override 80 @Override
134 public int hashCode() { 81 public int hashCode() {
135 - return Objects.hash(topic, leader, candidates, epoch, electedTime); 82 + return Objects.hash(topic, leader, candidates);
136 } 83 }
137 84
138 @Override 85 @Override
...@@ -144,9 +91,7 @@ public class Leadership { ...@@ -144,9 +91,7 @@ public class Leadership {
144 final Leadership other = (Leadership) obj; 91 final Leadership other = (Leadership) obj;
145 return Objects.equals(this.topic, other.topic) && 92 return Objects.equals(this.topic, other.topic) &&
146 Objects.equals(this.leader, other.leader) && 93 Objects.equals(this.leader, other.leader) &&
147 - Objects.equals(this.candidates, other.candidates) && 94 + Objects.equals(this.candidates, other.candidates);
148 - Objects.equals(this.epoch, other.epoch) &&
149 - Objects.equals(this.electedTime, other.electedTime);
150 } 95 }
151 return false; 96 return false;
152 } 97 }
...@@ -157,8 +102,6 @@ public class Leadership { ...@@ -157,8 +102,6 @@ public class Leadership {
157 .add("topic", topic) 102 .add("topic", topic)
158 .add("leader", leader) 103 .add("leader", leader)
159 .add("candidates", candidates) 104 .add("candidates", candidates)
160 - .add("epoch", epoch)
161 - .add("electedTime", new DateTime(electedTime))
162 .toString(); 105 .toString();
163 } 106 }
164 } 107 }
......
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.cluster;
17 +
18 +/**
19 + * Interface for administratively manipulating leadership assignments.
20 + */
21 +public interface LeadershipAdminService {
22 +
23 + /**
24 + * Attempts to assign leadership for a topic to a specified node.
25 + * @param topic leadership topic
26 + * @param nodeId identifier of the node to be made leader
27 + * @return true is the transfer was successfully executed. This method returns {@code false}
28 + * if {@code nodeId} is not one of the candidates for for the topic.
29 + */
30 + boolean transferLeadership(String topic, NodeId nodeId);
31 +
32 + /**
33 + * Make a node to be the next leader by promoting it to top of candidate list.
34 + * @param topic leadership topic
35 + * @param nodeId identifier of node to be next leader
36 + * @return {@code true} if nodeId is now the top candidate. This method returns {@code false}
37 + * if {@code nodeId} is not one of the candidates for for the topic.
38 + */
39 + boolean promoteToTopOfCandidateList(String topic, NodeId nodeId);
40 +
41 + /**
42 + * Removes all active leadership registrations for a given node.
43 + * <p>
44 + * This method will also evict the node from leaderships that it currently owns.
45 + * @param nodeId node identifier
46 + */
47 + void unregister(NodeId nodeId);
48 +}
...@@ -27,33 +27,25 @@ import com.google.common.base.MoreObjects; ...@@ -27,33 +27,25 @@ import com.google.common.base.MoreObjects;
27 public class LeadershipEvent extends AbstractEvent<LeadershipEvent.Type, Leadership> { 27 public class LeadershipEvent extends AbstractEvent<LeadershipEvent.Type, Leadership> {
28 28
29 /** 29 /**
30 - * Type of leadership-related events. 30 + * Type of leadership events.
31 */ 31 */
32 public enum Type { 32 public enum Type {
33 /** 33 /**
34 - * Signifies that the leader has been elected. 34 + * Signifies a change in both the leader as well as change to the list of candidates. Keep in mind though that
35 - * The event subject is the new leader. 35 + * the first node entering the race will trigger this event as it will become a candidate and automatically get
36 - * This event does not guarantee accurate candidate information. 36 + * promoted to become leader.
37 */ 37 */
38 - LEADER_ELECTED, 38 + LEADER_AND_CANDIDATES_CHANGED,
39 39
40 /** 40 /**
41 - * Signifies that the leader has been re-elected. 41 + * Signifies that the leader for a topic has changed.
42 - * The event subject is the leader.
43 - * This event does not guarantee accurate candidate information.
44 */ 42 */
45 - LEADER_REELECTED, 43 + // TODO: We may not need this. We currently do not support a way for a current leader to step down
44 + // while still reamining a candidate
45 + LEADER_CHANGED,
46 46
47 /** 47 /**
48 - * Signifies that the leader has been booted and lost leadership. 48 + * Signifies a change in the list of candidates for a topic.
49 - * The event subject is the former leader.
50 - * This event does not guarantee accurate candidate information.
51 - */
52 - LEADER_BOOTED,
53 -
54 - /**
55 - * Signifies that the list of candidates for leadership for a topic has
56 - * changed. This event does not guarantee accurate leader information.
57 */ 49 */
58 CANDIDATES_CHANGED 50 CANDIDATES_CHANGED
59 } 51 }
......
...@@ -17,87 +17,73 @@ package org.onosproject.cluster; ...@@ -17,87 +17,73 @@ package org.onosproject.cluster;
17 17
18 import org.onosproject.event.ListenerService; 18 import org.onosproject.event.ListenerService;
19 19
20 +import com.google.common.base.Objects;
21 +import com.google.common.collect.ImmutableList;
22 +import com.google.common.collect.ImmutableMap;
23 +import com.google.common.collect.Maps;
24 +
20 import java.util.List; 25 import java.util.List;
21 import java.util.Map; 26 import java.util.Map;
22 import java.util.Set; 27 import java.util.Set;
23 -import java.util.concurrent.CompletableFuture;
24 28
25 /** 29 /**
26 * Service for leader election. 30 * Service for leader election.
31 + * <p>
27 * Leadership contests are organized around topics. A instance can join the 32 * Leadership contests are organized around topics. A instance can join the
28 * leadership race for a topic or withdraw from a race it has previously joined. 33 * leadership race for a topic or withdraw from a race it has previously joined.
34 + * <p>
29 * Listeners can be added to receive notifications asynchronously for various 35 * Listeners can be added to receive notifications asynchronously for various
30 * leadership contests. 36 * leadership contests.
37 + * <p>
38 + * When a node gets elected as a leader for a topic, all nodes receive notifications
39 + * indicating a change in leadership.
31 */ 40 */
32 public interface LeadershipService 41 public interface LeadershipService
33 extends ListenerService<LeadershipEvent, LeadershipEventListener> { 42 extends ListenerService<LeadershipEvent, LeadershipEventListener> {
34 43
35 /** 44 /**
36 - * Returns the current leader for the topic. 45 + * Returns the {@link NodeId node identifier} that is the current leader for a topic.
37 * 46 *
38 - * @param path topic 47 + * @param topic leadership topic
39 - * @return nodeId of the leader, null if so such topic exists. 48 + * @return node identifier of the current leader; {@code null} if there is no leader for the topic
40 */ 49 */
41 - NodeId getLeader(String path); 50 + default NodeId getLeader(String topic) {
51 + Leadership leadership = getLeadership(topic);
52 + return leadership == null ? null : leadership.leaderNodeId();
53 + }
42 54
43 /** 55 /**
44 - * Returns the current leadership info for the topic. 56 + * Returns the current {@link Leadership leadership} for a topic.
45 * 57 *
46 - * @param path topic 58 + * @param topic leadership topic
47 - * @return leadership info or null if so such topic exists. 59 + * @return leadership or {@code null} if no such topic exists
48 */ 60 */
49 - Leadership getLeadership(String path); 61 + Leadership getLeadership(String topic);
50 62
51 /** 63 /**
52 - * Returns the set of topics owned by the specified node. 64 + * Returns the set of topics owned by the specified {@link NodeId node}.
53 * 65 *
54 - * @param nodeId node Id. 66 + * @param nodeId node identifier.
55 * @return set of topics for which this node is the current leader. 67 * @return set of topics for which this node is the current leader.
56 */ 68 */
57 - Set<String> ownedTopics(NodeId nodeId); 69 + default Set<String> ownedTopics(NodeId nodeId) {
70 + return Maps.filterValues(getLeaderBoard(), v -> Objects.equal(nodeId, v.leaderNodeId())).keySet();
71 + }
58 72
59 /** 73 /**
60 - * Joins the leadership contest. 74 + * Enters a leadership contest.
61 * 75 *
62 - * @param path topic for which this controller node wishes to be a leader 76 + * @param topic leadership topic
63 * @return {@code Leadership} future 77 * @return {@code Leadership} future
64 */ 78 */
65 - CompletableFuture<Leadership> runForLeadership(String path); 79 + Leadership runForLeadership(String topic);
66 80
67 /** 81 /**
68 * Withdraws from a leadership contest. 82 * Withdraws from a leadership contest.
69 * 83 *
70 - * @param path topic for which this controller node no longer wishes to be a leader 84 + * @param topic leadership topic
71 - * @return future that is successfully completed when withdraw is done
72 - */
73 - CompletableFuture<Void> withdraw(String path);
74 -
75 - /**
76 - * If the local nodeId is the leader for specified topic, this method causes it to
77 - * step down temporarily from leadership.
78 - * <p>
79 - * The node will continue to be in contention for leadership and can
80 - * potentially become the leader again if and when it becomes the highest
81 - * priority candidate
82 - * <p>
83 - * If the local nodeId is not the leader, this method will make no changes and
84 - * simply return false.
85 - *
86 - * @param path topic for which this controller node should give up leadership
87 - * @return true if this node stepped down from leadership, false otherwise
88 - */
89 - boolean stepdown(String path);
90 -
91 - /**
92 - * Moves the specified nodeId to the top of the candidates list for the topic.
93 - * <p>
94 - * If the node is not a candidate for this topic, this method will be a noop.
95 - *
96 - * @param path leadership topic
97 - * @param nodeId nodeId to make the top candidate
98 - * @return true if nodeId is now the top candidate, false otherwise
99 */ 85 */
100 - boolean makeTopCandidate(String path, NodeId nodeId); 86 + void withdraw(String topic);
101 87
102 /** 88 /**
103 * Returns the current leader board. 89 * Returns the current leader board.
...@@ -107,18 +93,22 @@ public interface LeadershipService ...@@ -107,18 +93,22 @@ public interface LeadershipService
107 Map<String, Leadership> getLeaderBoard(); 93 Map<String, Leadership> getLeaderBoard();
108 94
109 /** 95 /**
110 - * Returns the candidates for all known topics. 96 + * Returns the candidate nodes for each topic.
111 * 97 *
112 * @return A mapping from topics to corresponding list of candidates. 98 * @return A mapping from topics to corresponding list of candidates.
113 */ 99 */
114 - Map<String, List<NodeId>> getCandidates(); 100 + default Map<String, List<NodeId>> getCandidates() {
101 + return ImmutableMap.copyOf(Maps.transformValues(getLeaderBoard(), v -> ImmutableList.copyOf(v.candidates())));
102 + }
115 103
116 /** 104 /**
117 - * Returns the candidates for a given topic. 105 + * Returns the candidate nodes for a given topic.
118 * 106 *
119 - * @param path topic 107 + * @param topic leadership topic
120 - * @return A lists of NodeIds, which may be empty. 108 + * @return A lists of {@link NodeId nodeIds}, which may be empty.
121 */ 109 */
122 - List<NodeId> getCandidates(String path); 110 + default List<NodeId> getCandidates(String topic) {
123 - 111 + Leadership leadership = getLeadership(topic);
112 + return leadership == null ? ImmutableList.of() : ImmutableList.copyOf(leadership.candidates());
113 + }
124 } 114 }
...\ No newline at end of file ...\ No newline at end of file
......
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.cluster;
17 +
18 +import java.util.Map;
19 +import org.onosproject.store.Store;
20 +
21 +/**
22 + * Store interface for managing {@link LeadershipService} state.
23 + */
24 +public interface LeadershipStore extends Store<LeadershipEvent, LeadershipStoreDelegate> {
25 +
26 + /**
27 + * Adds registration for the local instance to be leader for topic.
28 + *
29 + * @param topic leadership topic
30 + * @return Updated leadership after operation is completed
31 + */
32 + Leadership addRegistration(String topic);
33 +
34 + /**
35 + * Unregisters the local instance from leadership contest for topic.
36 + *
37 + * @param topic leadership topic
38 + */
39 + void removeRegistration(String topic);
40 +
41 + /**
42 + * Unregisters an instance from all leadership contests.
43 + *
44 + * @param nodeId node identifier
45 + */
46 + void removeRegistration(NodeId nodeId);
47 +
48 + /**
49 + * Updates state so that given node is leader for a topic.
50 + *
51 + * @param topic leadership topic
52 + * @param toNodeId identifier of the desired leader
53 + * @return {@code true} if the transfer succeeded; {@code false} otherwise.
54 + * This method can return {@code false} if the node is not registered for the topic
55 + */
56 + boolean moveLeadership(String topic, NodeId toNodeId);
57 +
58 + /**
59 + * Attempts to make a node the top candidate.
60 + *
61 + * @param topic leadership topic
62 + * @param nodeId node identifier
63 + * @return {@code true} if the specified node is now the top candidate.
64 + * This method will return {@code false} if the node is not registered for the topic
65 + */
66 + boolean makeTopCandidate(String topic, NodeId nodeId);
67 +
68 + /**
69 + * Returns the current leadership for topic.
70 + *
71 + * @param topic leadership topic
72 + * @return current leadership
73 + */
74 + Leadership getLeadership(String topic);
75 +
76 + /**
77 + * Return current leadership for all topics.
78 + *
79 + * @return topic to leadership mapping
80 + */
81 + Map<String, Leadership> getLeaderships();
82 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.cluster;
17 +
18 +import org.onosproject.store.StoreDelegate;
19 +
20 +/**
21 + * {@link LeadershipStore} delegate abstraction.
22 + */
23 +public interface LeadershipStoreDelegate extends StoreDelegate<LeadershipEvent> {
24 +}
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
15 */ 15 */
16 package org.onosproject.cluster; 16 package org.onosproject.cluster;
17 17
18 +import java.util.Arrays;
19 +
18 import org.junit.Test; 20 import org.junit.Test;
19 21
20 import com.google.common.testing.EqualsTester; 22 import com.google.common.testing.EqualsTester;
...@@ -28,10 +30,11 @@ import static org.junit.Assert.assertThat; ...@@ -28,10 +30,11 @@ import static org.junit.Assert.assertThat;
28 public class LeadershipEventTest { 30 public class LeadershipEventTest {
29 private final NodeId node1 = new NodeId("1"); 31 private final NodeId node1 = new NodeId("1");
30 private final NodeId node2 = new NodeId("2"); 32 private final NodeId node2 = new NodeId("2");
31 - private final Leadership lead1 = new Leadership("topic1", node1, 1L, 2L); 33 + private final Leadership lead1 = new Leadership("topic1", new Leader(node1, 1L, 2L), Arrays.asList(node1));
32 - private final Leadership lead2 = new Leadership("topic1", node2, 1L, 2L); 34 + private final Leadership lead2 = new Leadership("topic1", new Leader(node1, 1L, 2L), Arrays.asList(node1, node2));
35 + private final Leadership lead3 = new Leadership("topic1", new Leader(node2, 1L, 2L), Arrays.asList(node2));
33 private final LeadershipEvent event1 = 36 private final LeadershipEvent event1 =
34 - new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, lead1); 37 + new LeadershipEvent(LeadershipEvent.Type.LEADER_CHANGED, lead1);
35 private final long time = System.currentTimeMillis(); 38 private final long time = System.currentTimeMillis();
36 private final LeadershipEvent event2 = 39 private final LeadershipEvent event2 =
37 new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED, 40 new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED,
...@@ -40,11 +43,9 @@ public class LeadershipEventTest { ...@@ -40,11 +43,9 @@ public class LeadershipEventTest {
40 new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED, 43 new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED,
41 lead2, time); 44 lead2, time);
42 private final LeadershipEvent event3 = 45 private final LeadershipEvent event3 =
43 - new LeadershipEvent(LeadershipEvent.Type.LEADER_BOOTED, lead1); 46 + new LeadershipEvent(LeadershipEvent.Type.LEADER_CHANGED, lead2);
44 private final LeadershipEvent event4 = 47 private final LeadershipEvent event4 =
45 - new LeadershipEvent(LeadershipEvent.Type.LEADER_REELECTED, lead1); 48 + new LeadershipEvent(LeadershipEvent.Type.LEADER_AND_CANDIDATES_CHANGED, lead3);
46 - private final LeadershipEvent event5 =
47 - new LeadershipEvent(LeadershipEvent.Type.LEADER_REELECTED, lead2);
48 49
49 /** 50 /**
50 * Tests for proper operation of equals(), hashCode() and toString() methods. 51 * Tests for proper operation of equals(), hashCode() and toString() methods.
...@@ -56,7 +57,6 @@ public class LeadershipEventTest { ...@@ -56,7 +57,6 @@ public class LeadershipEventTest {
56 .addEqualityGroup(event2, sameAsEvent2) 57 .addEqualityGroup(event2, sameAsEvent2)
57 .addEqualityGroup(event3) 58 .addEqualityGroup(event3)
58 .addEqualityGroup(event4) 59 .addEqualityGroup(event4)
59 - .addEqualityGroup(event5)
60 .testEquals(); 60 .testEquals();
61 } 61 }
62 62
...@@ -65,7 +65,7 @@ public class LeadershipEventTest { ...@@ -65,7 +65,7 @@ public class LeadershipEventTest {
65 */ 65 */
66 @Test 66 @Test
67 public void checkConstruction() { 67 public void checkConstruction() {
68 - assertThat(event1.type(), is(LeadershipEvent.Type.LEADER_ELECTED)); 68 + assertThat(event1.type(), is(LeadershipEvent.Type.LEADER_CHANGED));
69 assertThat(event1.subject(), is(lead1)); 69 assertThat(event1.subject(), is(lead1));
70 70
71 assertThat(event2.time(), is(time)); 71 assertThat(event2.time(), is(time));
......
...@@ -18,7 +18,6 @@ package org.onosproject.cluster; ...@@ -18,7 +18,6 @@ package org.onosproject.cluster;
18 import java.util.List; 18 import java.util.List;
19 import java.util.Map; 19 import java.util.Map;
20 import java.util.Set; 20 import java.util.Set;
21 -import java.util.concurrent.CompletableFuture;
22 21
23 /** 22 /**
24 * Test adapter for leadership service. 23 * Test adapter for leadership service.
...@@ -41,13 +40,12 @@ public class LeadershipServiceAdapter implements LeadershipService { ...@@ -41,13 +40,12 @@ public class LeadershipServiceAdapter implements LeadershipService {
41 } 40 }
42 41
43 @Override 42 @Override
44 - public CompletableFuture<Leadership> runForLeadership(String path) { 43 + public Leadership runForLeadership(String path) {
45 return null; 44 return null;
46 } 45 }
47 46
48 @Override 47 @Override
49 - public CompletableFuture<Void> withdraw(String path) { 48 + public void withdraw(String path) {
50 - return null;
51 } 49 }
52 50
53 @Override 51 @Override
...@@ -74,14 +72,4 @@ public class LeadershipServiceAdapter implements LeadershipService { ...@@ -74,14 +72,4 @@ public class LeadershipServiceAdapter implements LeadershipService {
74 public List<NodeId> getCandidates(String path) { 72 public List<NodeId> getCandidates(String path) {
75 return null; 73 return null;
76 } 74 }
77 -
78 - @Override
79 - public boolean stepdown(String path) {
80 - return false;
81 - }
82 -
83 - @Override
84 - public boolean makeTopCandidate(String path, NodeId nodeId) {
85 - return false;
86 - }
87 } 75 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -15,12 +15,13 @@ ...@@ -15,12 +15,13 @@
15 */ 15 */
16 package org.onosproject.cluster; 16 package org.onosproject.cluster;
17 17
18 +import java.util.Arrays;
19 +
18 import org.junit.Test; 20 import org.junit.Test;
19 21
20 import com.google.common.collect.ImmutableList; 22 import com.google.common.collect.ImmutableList;
21 import com.google.common.testing.EqualsTester; 23 import com.google.common.testing.EqualsTester;
22 24
23 -import static org.hamcrest.Matchers.contains;
24 import static org.hamcrest.Matchers.hasSize; 25 import static org.hamcrest.Matchers.hasSize;
25 import static org.hamcrest.Matchers.is; 26 import static org.hamcrest.Matchers.is;
26 import static org.junit.Assert.assertThat; 27 import static org.junit.Assert.assertThat;
...@@ -31,16 +32,14 @@ import static org.junit.Assert.assertThat; ...@@ -31,16 +32,14 @@ import static org.junit.Assert.assertThat;
31 public class LeadershipTest { 32 public class LeadershipTest {
32 private final NodeId node1 = new NodeId("1"); 33 private final NodeId node1 = new NodeId("1");
33 private final NodeId node2 = new NodeId("2"); 34 private final NodeId node2 = new NodeId("2");
34 - private final Leadership lead1 = new Leadership("topic1", node1, 1L, 2L); 35 + private final Leadership lead1 = new Leadership("topic1", new Leader(node1, 1L, 2L), Arrays.asList(node1));
35 - private final Leadership sameAsLead1 = new Leadership("topic1", node1, 1L, 2L); 36 + private final Leadership sameAsLead1 = new Leadership("topic1", new Leader(node1, 1L, 2L), Arrays.asList(node1));
36 - private final Leadership lead2 = new Leadership("topic2", node1, 1L, 2L); 37 + private final Leadership lead2 = new Leadership("topic2", new Leader(node1, 1L, 2L), Arrays.asList(node1));
37 - private final Leadership lead3 = new Leadership("topic1", node1, 2L, 2L); 38 + private final Leadership lead3 = new Leadership("topic1", new Leader(node1, 2L, 2L), Arrays.asList(node1));
38 - private final Leadership lead4 = new Leadership("topic1", node1, 3L, 2L); 39 + private final Leadership lead4 = new Leadership("topic1", new Leader(node1, 3L, 2L), Arrays.asList(node1));
39 - private final Leadership lead5 = new Leadership("topic1", node1, 3L, 3L); 40 + private final Leadership lead5 = new Leadership("topic1", new Leader(node1, 3L, 3L), Arrays.asList(node1));
40 - private final Leadership lead6 = new Leadership("topic1", node1, 41 + private final Leadership lead6 = new Leadership("topic1", new Leader(node2, 1L, 2L), Arrays.asList(node2, node1));
41 - ImmutableList.of(node2), 1L, 2L); 42 + private final Leadership lead7 = new Leadership("topic1", null, ImmutableList.of());
42 - private final Leadership lead7 = new Leadership("topic1",
43 - ImmutableList.of(node2), 1L, 2L);
44 43
45 /** 44 /**
46 * Tests for proper operation of equals(), hashCode() and toString() methods. 45 * Tests for proper operation of equals(), hashCode() and toString() methods.
...@@ -64,12 +63,10 @@ public class LeadershipTest { ...@@ -64,12 +63,10 @@ public class LeadershipTest {
64 */ 63 */
65 @Test 64 @Test
66 public void checkConstruction() { 65 public void checkConstruction() {
67 - assertThat(lead6.electedTime(), is(2L)); 66 + assertThat(lead6.leader(), is(new Leader(node2, 1L, 2L)));
68 - assertThat(lead6.epoch(), is(1L));
69 - assertThat(lead6.leader(), is(node1));
70 assertThat(lead6.topic(), is("topic1")); 67 assertThat(lead6.topic(), is("topic1"));
71 - assertThat(lead6.candidates(), hasSize(1)); 68 + assertThat(lead6.candidates(), hasSize(2));
72 - assertThat(lead6.candidates(), contains(node2)); 69 + assertThat(lead6.candidates().get(1), is(node1));
70 + assertThat(lead6.candidates().get(0), is(node2));
73 } 71 }
74 -
75 } 72 }
......
...@@ -17,20 +17,22 @@ package org.onosproject.store.trivial; ...@@ -17,20 +17,22 @@ package org.onosproject.store.trivial;
17 17
18 import static com.google.common.base.Preconditions.checkArgument; 18 import static com.google.common.base.Preconditions.checkArgument;
19 19
20 +import java.util.Arrays;
20 import java.util.List; 21 import java.util.List;
21 import java.util.Map; 22 import java.util.Map;
22 import java.util.Map.Entry; 23 import java.util.Map.Entry;
23 import java.util.Set; 24 import java.util.Set;
24 -import java.util.concurrent.CompletableFuture;
25 import java.util.concurrent.ConcurrentHashMap; 25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.CopyOnWriteArraySet; 26 import java.util.concurrent.CopyOnWriteArraySet;
27 import java.util.stream.Collectors; 27 import java.util.stream.Collectors;
28 28
29 +import org.apache.felix.scr.annotations.Activate;
29 import org.apache.felix.scr.annotations.Component; 30 import org.apache.felix.scr.annotations.Component;
30 import org.apache.felix.scr.annotations.Reference; 31 import org.apache.felix.scr.annotations.Reference;
31 import org.apache.felix.scr.annotations.ReferenceCardinality; 32 import org.apache.felix.scr.annotations.ReferenceCardinality;
32 import org.apache.felix.scr.annotations.Service; 33 import org.apache.felix.scr.annotations.Service;
33 import org.onosproject.cluster.ClusterService; 34 import org.onosproject.cluster.ClusterService;
35 +import org.onosproject.cluster.Leader;
34 import org.onosproject.cluster.Leadership; 36 import org.onosproject.cluster.Leadership;
35 import org.onosproject.cluster.LeadershipEvent; 37 import org.onosproject.cluster.LeadershipEvent;
36 import org.onosproject.cluster.LeadershipEvent.Type; 38 import org.onosproject.cluster.LeadershipEvent.Type;
...@@ -53,8 +55,15 @@ public class SimpleLeadershipManager implements LeadershipService { ...@@ -53,8 +55,15 @@ public class SimpleLeadershipManager implements LeadershipService {
53 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 55 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
54 private ClusterService clusterService; 56 private ClusterService clusterService;
55 57
58 + private NodeId localNodeId;
59 +
56 private Map<String, Boolean> elections = new ConcurrentHashMap<>(); 60 private Map<String, Boolean> elections = new ConcurrentHashMap<>();
57 61
62 + @Activate
63 + public void activate() {
64 + localNodeId = clusterService.getLocalNode().id();
65 + }
66 +
58 @Override 67 @Override
59 public NodeId getLeader(String path) { 68 public NodeId getLeader(String path) {
60 return elections.get(path) ? clusterService.getLocalNode().id() : null; 69 return elections.get(path) ? clusterService.getLocalNode().id() : null;
...@@ -63,7 +72,8 @@ public class SimpleLeadershipManager implements LeadershipService { ...@@ -63,7 +72,8 @@ public class SimpleLeadershipManager implements LeadershipService {
63 @Override 72 @Override
64 public Leadership getLeadership(String path) { 73 public Leadership getLeadership(String path) {
65 checkArgument(path != null); 74 checkArgument(path != null);
66 - return elections.get(path) ? new Leadership(path, clusterService.getLocalNode().id(), 0, 0) : null; 75 + return elections.get(path) ?
76 + new Leadership(path, new Leader(localNodeId, 0, 0), Arrays.asList(localNodeId)) : null;
67 } 77 }
68 78
69 @Override 79 @Override
...@@ -77,23 +87,22 @@ public class SimpleLeadershipManager implements LeadershipService { ...@@ -77,23 +87,22 @@ public class SimpleLeadershipManager implements LeadershipService {
77 } 87 }
78 88
79 @Override 89 @Override
80 - public CompletableFuture<Leadership> runForLeadership(String path) { 90 + public Leadership runForLeadership(String path) {
81 elections.put(path, true); 91 elections.put(path, true);
92 + Leadership leadership = new Leadership(path, new Leader(localNodeId, 0, 0), Arrays.asList(localNodeId));
82 for (LeadershipEventListener listener : listeners) { 93 for (LeadershipEventListener listener : listeners) {
83 - listener.event(new LeadershipEvent(Type.LEADER_ELECTED, 94 + listener.event(new LeadershipEvent(Type.LEADER_AND_CANDIDATES_CHANGED, leadership));
84 - new Leadership(path, clusterService.getLocalNode().id(), 0, 0)));
85 } 95 }
86 - return CompletableFuture.completedFuture(new Leadership(path, clusterService.getLocalNode().id(), 0, 0)); 96 + return leadership;
87 } 97 }
88 98
89 @Override 99 @Override
90 - public CompletableFuture<Void> withdraw(String path) { 100 + public void withdraw(String path) {
91 elections.remove(path); 101 elections.remove(path);
92 for (LeadershipEventListener listener : listeners) { 102 for (LeadershipEventListener listener : listeners) {
93 - listener.event(new LeadershipEvent(Type.LEADER_BOOTED, 103 + listener.event(new LeadershipEvent(Type.LEADER_AND_CANDIDATES_CHANGED,
94 - new Leadership(path, clusterService.getLocalNode().id(), 0, 0))); 104 + new Leadership(path, null, Arrays.asList())));
95 } 105 }
96 - return CompletableFuture.completedFuture(null);
97 } 106 }
98 107
99 @Override 108 @Override
...@@ -122,14 +131,4 @@ public class SimpleLeadershipManager implements LeadershipService { ...@@ -122,14 +131,4 @@ public class SimpleLeadershipManager implements LeadershipService {
122 public List<NodeId> getCandidates(String path) { 131 public List<NodeId> getCandidates(String path) {
123 return null; 132 return null;
124 } 133 }
125 -
126 - @Override
127 - public boolean stepdown(String path) {
128 - throw new UnsupportedOperationException();
129 - }
130 -
131 - @Override
132 - public boolean makeTopCandidate(String path, NodeId nodeId) {
133 - throw new UnsupportedOperationException();
134 - }
135 } 134 }
......
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.cluster.impl;
17 +
18 +import static org.slf4j.LoggerFactory.getLogger;
19 +
20 +import java.util.Map;
21 +import java.util.concurrent.Executors;
22 +import java.util.concurrent.ScheduledExecutorService;
23 +import java.util.concurrent.TimeUnit;
24 +
25 +import org.apache.felix.scr.annotations.Activate;
26 +import org.apache.felix.scr.annotations.Component;
27 +import org.apache.felix.scr.annotations.Deactivate;
28 +import org.apache.felix.scr.annotations.Reference;
29 +import org.apache.felix.scr.annotations.ReferenceCardinality;
30 +import org.apache.felix.scr.annotations.Service;
31 +import org.onlab.util.Tools;
32 +import org.onosproject.cluster.ClusterService;
33 +import org.onosproject.cluster.ControllerNode;
34 +import org.onosproject.cluster.Leadership;
35 +import org.onosproject.cluster.LeadershipAdminService;
36 +import org.onosproject.cluster.LeadershipEvent;
37 +import org.onosproject.cluster.LeadershipEventListener;
38 +import org.onosproject.cluster.LeadershipService;
39 +import org.onosproject.cluster.LeadershipStore;
40 +import org.onosproject.cluster.LeadershipStoreDelegate;
41 +import org.onosproject.cluster.NodeId;
42 +import org.onosproject.event.AbstractListenerManager;
43 +import org.slf4j.Logger;
44 +
45 +import com.google.common.collect.Maps;
46 +
47 +/**
48 + * Implementation of {@link LeadershipService} and {@link LeadershipAdminService}.
49 + */
50 +@Component(immediate = true)
51 +@Service
52 +public class LeadershipManager
53 + extends AbstractListenerManager<LeadershipEvent, LeadershipEventListener>
54 + implements LeadershipService, LeadershipAdminService {
55 +
56 + private final Logger log = getLogger(getClass());
57 +
58 + private LeadershipStoreDelegate delegate = this::post;
59 +
60 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
61 + protected ClusterService clusterService;
62 +
63 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
64 + protected LeadershipStore store;
65 +
66 + private NodeId localNodeId;
67 +
68 + private final ScheduledExecutorService deadlockDetector =
69 + Executors.newSingleThreadScheduledExecutor(Tools.groupedThreads("onos/leadership", ""));
70 +
71 + @Activate
72 + public void activate() {
73 + localNodeId = clusterService.getLocalNode().id();
74 + store.setDelegate(delegate);
75 + eventDispatcher.addSink(LeadershipEvent.class, listenerRegistry);
76 + deadlockDetector.scheduleWithFixedDelay(() -> clusterService.getNodes()
77 + .stream()
78 + .map(ControllerNode::id)
79 + .filter(id -> clusterService.getState(id) != ControllerNode.State.ACTIVE)
80 + .forEach(this::unregister), 0, 2, TimeUnit.SECONDS);
81 + log.info("Started");
82 + }
83 +
84 + @Deactivate
85 + public void deactivate() {
86 + deadlockDetector.shutdown();
87 + Maps.filterValues(store.getLeaderships(), v -> v.candidates().contains(localNodeId))
88 + .keySet()
89 + .forEach(this::withdraw);
90 + store.unsetDelegate(delegate);
91 + eventDispatcher.removeSink(LeadershipEvent.class);
92 + log.info("Stopped");
93 + }
94 +
95 + @Override
96 + public Leadership getLeadership(String topic) {
97 + return store.getLeadership(topic);
98 + }
99 +
100 + @Override
101 + public Leadership runForLeadership(String topic) {
102 + return store.addRegistration(topic);
103 + }
104 +
105 + @Override
106 + public void withdraw(String topic) {
107 + store.removeRegistration(topic);
108 + }
109 +
110 + @Override
111 + public Map<String, Leadership> getLeaderBoard() {
112 + return store.getLeaderships();
113 + }
114 +
115 + @Override
116 + public boolean transferLeadership(String topic, NodeId to) {
117 + return store.moveLeadership(topic, to);
118 + }
119 +
120 + @Override
121 + public void unregister(NodeId nodeId) {
122 + store.removeRegistration(nodeId);
123 + }
124 +
125 + @Override
126 + public boolean promoteToTopOfCandidateList(String topic, NodeId nodeId) {
127 + return store.makeTopCandidate(topic, nodeId);
128 + }
129 +}
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.store.cluster.impl;
17 +
18 +import static org.slf4j.LoggerFactory.getLogger;
19 +
20 +import java.util.ArrayList;
21 +import java.util.List;
22 +import java.util.Map;
23 +import java.util.stream.Collectors;
24 +
25 +import org.apache.felix.scr.annotations.Activate;
26 +import org.apache.felix.scr.annotations.Component;
27 +import org.apache.felix.scr.annotations.Deactivate;
28 +import org.apache.felix.scr.annotations.Reference;
29 +import org.apache.felix.scr.annotations.ReferenceCardinality;
30 +import org.apache.felix.scr.annotations.Service;
31 +import org.onosproject.cluster.ClusterService;
32 +import org.onosproject.cluster.Leader;
33 +import org.onosproject.cluster.Leadership;
34 +import org.onosproject.cluster.LeadershipEvent;
35 +import org.onosproject.cluster.LeadershipStore;
36 +import org.onosproject.cluster.LeadershipStoreDelegate;
37 +import org.onosproject.cluster.NodeId;
38 +import org.onosproject.store.AbstractStore;
39 +import org.onosproject.store.serializers.KryoNamespaces;
40 +import org.onosproject.store.service.ConsistentMap;
41 +import org.onosproject.store.service.MapEventListener;
42 +import org.onosproject.store.service.Serializer;
43 +import org.onosproject.store.service.StorageService;
44 +import org.onosproject.store.service.Versioned;
45 +import org.slf4j.Logger;
46 +
47 +import com.google.common.base.MoreObjects;
48 +import com.google.common.base.Objects;
49 +import com.google.common.collect.ImmutableList;
50 +import com.google.common.collect.ImmutableMap;
51 +import com.google.common.collect.ImmutableSet;
52 +import com.google.common.collect.Maps;
53 +import com.google.common.collect.Sets;
54 +
55 +/**
56 + * Implementation of {@code LeadershipStore} backed by {@link ConsistentMap}.
57 + */
58 +@Service
59 +@Component(immediate = true, enabled = true)
60 +public class DistributedLeadershipStore
61 + extends AbstractStore<LeadershipEvent, LeadershipStoreDelegate>
62 + implements LeadershipStore {
63 +
64 + private static final Logger log = getLogger(DistributedLeadershipStore.class);
65 +
66 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
67 + protected ClusterService clusterService;
68 +
69 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
70 + protected StorageService storageService;
71 +
72 + protected NodeId localNodeId;
73 + protected ConsistentMap<String, InternalLeadership> leadershipMap;
74 + private final MapEventListener<String, InternalLeadership> leadershipChangeListener =
75 + event -> {
76 + Leadership oldValue = InternalLeadership.toLeadership(Versioned.valueOrNull(event.oldValue()));
77 + Leadership newValue = InternalLeadership.toLeadership(Versioned.valueOrNull(event.newValue()));
78 + boolean leaderChanged =
79 + !Objects.equal(oldValue == null ? null : oldValue.leader(), newValue.leader());
80 + boolean candidatesChanged =
81 + !Sets.symmetricDifference(Sets.newHashSet(oldValue == null ?
82 + ImmutableSet.<NodeId>of() : oldValue.candidates()),
83 + Sets.newHashSet(newValue.candidates())).isEmpty();
84 + LeadershipEvent.Type eventType = null;
85 + if (leaderChanged && candidatesChanged) {
86 + eventType = LeadershipEvent.Type.LEADER_AND_CANDIDATES_CHANGED;
87 + }
88 + if (leaderChanged && !candidatesChanged) {
89 + eventType = LeadershipEvent.Type.LEADER_CHANGED;
90 + }
91 + if (!leaderChanged && candidatesChanged) {
92 + eventType = LeadershipEvent.Type.CANDIDATES_CHANGED;
93 + }
94 + notifyDelegate(new LeadershipEvent(eventType, newValue));
95 + };
96 +
97 + @Activate
98 + public void activate() {
99 + localNodeId = clusterService.getLocalNode().id();
100 + leadershipMap = storageService.<String, InternalLeadership>consistentMapBuilder()
101 + .withName("onos-leadership")
102 + .withPartitionsDisabled()
103 + .withRelaxedReadConsistency()
104 + .withSerializer(Serializer.using(KryoNamespaces.API, InternalLeadership.class))
105 + .build();
106 + leadershipMap.addListener(leadershipChangeListener);
107 + log.info("Started");
108 + }
109 +
110 + @Deactivate
111 + public void deactivate() {
112 + leadershipMap.removeListener(leadershipChangeListener);
113 + log.info("Stopped");
114 + }
115 +
116 + @Override
117 + public Leadership addRegistration(String topic) {
118 + Versioned<InternalLeadership> internalLeadership = leadershipMap.computeIf(topic,
119 + v -> v == null || !v.candidates().contains(localNodeId),
120 + (k, v) -> {
121 + if (v == null || v.candidates().isEmpty()) {
122 + return new InternalLeadership(topic,
123 + localNodeId,
124 + v == null ? 1 : v.term() + 1,
125 + System.currentTimeMillis(),
126 + ImmutableList.of(localNodeId));
127 + }
128 + List<NodeId> newCandidates = new ArrayList<>(v.candidates());
129 + newCandidates.add(localNodeId);
130 + return new InternalLeadership(topic, v.leader(), v.term(), v.termStartTime(), newCandidates);
131 + });
132 + return InternalLeadership.toLeadership(Versioned.valueOrNull(internalLeadership));
133 + }
134 +
135 + @Override
136 + public void removeRegistration(String topic) {
137 + removeRegistration(topic, localNodeId);
138 + }
139 +
140 + private void removeRegistration(String topic, NodeId nodeId) {
141 + leadershipMap.computeIf(topic,
142 + v -> v != null && v.candidates().contains(nodeId),
143 + (k, v) -> {
144 + List<NodeId> newCandidates = v.candidates()
145 + .stream()
146 + .filter(id -> !nodeId.equals(id))
147 + .collect(Collectors.toList());
148 + NodeId newLeader = nodeId.equals(v.leader()) ?
149 + newCandidates.size() > 0 ? newCandidates.get(0) : null : v.leader();
150 + long newTerm = newLeader == null || Objects.equal(newLeader, v.leader()) ?
151 + v.term() : v.term() + 1;
152 + long newTermStartTime = newLeader == null || Objects.equal(newLeader, v.leader()) ?
153 + v.termStartTime() : System.currentTimeMillis();
154 + return new InternalLeadership(topic, newLeader, newTerm, newTermStartTime, newCandidates);
155 + });
156 + }
157 +
158 + @Override
159 + public void removeRegistration(NodeId nodeId) {
160 + leadershipMap.entrySet()
161 + .stream()
162 + .filter(e -> e.getValue().value().candidates().contains(nodeId))
163 + .map(e -> e.getKey())
164 + .forEach(topic -> this.removeRegistration(topic, nodeId));
165 + }
166 +
167 + @Override
168 + public boolean moveLeadership(String topic, NodeId toNodeId) {
169 + Versioned<InternalLeadership> internalLeadership = leadershipMap.computeIf(topic,
170 + v -> v != null &&
171 + v.candidates().contains(toNodeId) &&
172 + !Objects.equal(v.leader(), toNodeId),
173 + (k, v) -> {
174 + List<NodeId> newCandidates = new ArrayList<>();
175 + newCandidates.add(toNodeId);
176 + newCandidates.addAll(v.candidates()
177 + .stream()
178 + .filter(id -> !toNodeId.equals(id))
179 + .collect(Collectors.toList()));
180 + return new InternalLeadership(topic,
181 + toNodeId,
182 + v.term() + 1,
183 + System.currentTimeMillis(),
184 + newCandidates);
185 + });
186 + return Objects.equal(toNodeId, Versioned.valueOrNull(internalLeadership).leader());
187 + }
188 +
189 + @Override
190 + public boolean makeTopCandidate(String topic, NodeId nodeId) {
191 + Versioned<InternalLeadership> internalLeadership = leadershipMap.computeIf(topic,
192 + v -> v != null &&
193 + v.candidates().contains(nodeId) &&
194 + !v.candidates().get(0).equals(nodeId),
195 + (k, v) -> {
196 + List<NodeId> newCandidates = new ArrayList<>();
197 + newCandidates.add(nodeId);
198 + newCandidates.addAll(v.candidates()
199 + .stream()
200 + .filter(id -> !nodeId.equals(id))
201 + .collect(Collectors.toList()));
202 + return new InternalLeadership(topic,
203 + v.leader(),
204 + v.term(),
205 + System.currentTimeMillis(),
206 + newCandidates);
207 + });
208 + return internalLeadership != null && nodeId.equals(internalLeadership.value().candidates().get(0));
209 + }
210 +
211 + @Override
212 + public Leadership getLeadership(String topic) {
213 + return InternalLeadership.toLeadership(Versioned.valueOrNull(leadershipMap.get(topic)));
214 + }
215 +
216 + @Override
217 + public Map<String, Leadership> getLeaderships() {
218 + Map<String, Leadership> leaderships = Maps.newHashMap();
219 + leadershipMap.entrySet().forEach(e -> {
220 + leaderships.put(e.getKey(), e.getValue().value().asLeadership());
221 + });
222 + return ImmutableMap.copyOf(leaderships);
223 + }
224 +
225 + private static class InternalLeadership {
226 + private final String topic;
227 + private final NodeId leader;
228 + private final long term;
229 + private final long termStartTime;
230 + private final List<NodeId> candidates;
231 +
232 + public InternalLeadership(String topic,
233 + NodeId leader,
234 + long term,
235 + long termStartTime,
236 + List<NodeId> candidates) {
237 + this.topic = topic;
238 + this.leader = leader;
239 + this.term = term;
240 + this.termStartTime = termStartTime;
241 + this.candidates = ImmutableList.copyOf(candidates);
242 + }
243 +
244 + public NodeId leader() {
245 + return this.leader;
246 + }
247 +
248 + public long term() {
249 + return term;
250 + }
251 +
252 + public long termStartTime() {
253 + return termStartTime;
254 + }
255 +
256 + public List<NodeId> candidates() {
257 + return candidates;
258 + }
259 +
260 + public Leadership asLeadership() {
261 + return new Leadership(topic, leader == null ?
262 + null : new Leader(leader, term, termStartTime), candidates);
263 + }
264 +
265 + public static Leadership toLeadership(InternalLeadership internalLeadership) {
266 + return internalLeadership == null ? null : internalLeadership.asLeadership();
267 + }
268 +
269 + @Override
270 + public String toString() {
271 + return MoreObjects.toStringHelper(getClass())
272 + .add("leader", leader)
273 + .add("term", term)
274 + .add("termStartTime", termStartTime)
275 + .add("candidates", candidates)
276 + .toString();
277 + }
278 + }
279 +}
...@@ -21,8 +21,6 @@ import org.apache.felix.scr.annotations.Deactivate; ...@@ -21,8 +21,6 @@ import org.apache.felix.scr.annotations.Deactivate;
21 import org.apache.felix.scr.annotations.Reference; 21 import org.apache.felix.scr.annotations.Reference;
22 import org.apache.felix.scr.annotations.ReferenceCardinality; 22 import org.apache.felix.scr.annotations.ReferenceCardinality;
23 import org.apache.felix.scr.annotations.Service; 23 import org.apache.felix.scr.annotations.Service;
24 -import org.onosproject.cluster.ClusterEvent;
25 -import org.onosproject.cluster.ClusterEventListener;
26 import org.onosproject.cluster.ClusterService; 24 import org.onosproject.cluster.ClusterService;
27 import org.onosproject.cluster.ControllerNode; 25 import org.onosproject.cluster.ControllerNode;
28 import org.onosproject.cluster.Leadership; 26 import org.onosproject.cluster.Leadership;
...@@ -76,7 +74,6 @@ public class IntentPartitionManager implements IntentPartitionService { ...@@ -76,7 +74,6 @@ public class IntentPartitionManager implements IntentPartitionService {
76 74
77 private ListenerRegistry<IntentPartitionEvent, IntentPartitionEventListener> listenerRegistry; 75 private ListenerRegistry<IntentPartitionEvent, IntentPartitionEventListener> listenerRegistry;
78 private LeadershipEventListener leaderListener = new InternalLeadershipListener(); 76 private LeadershipEventListener leaderListener = new InternalLeadershipListener();
79 - private ClusterEventListener clusterListener = new InternalClusterEventListener();
80 77
81 private ScheduledExecutorService executor = Executors 78 private ScheduledExecutorService executor = Executors
82 .newScheduledThreadPool(1); 79 .newScheduledThreadPool(1);
...@@ -84,7 +81,6 @@ public class IntentPartitionManager implements IntentPartitionService { ...@@ -84,7 +81,6 @@ public class IntentPartitionManager implements IntentPartitionService {
84 @Activate 81 @Activate
85 public void activate() { 82 public void activate() {
86 leadershipService.addListener(leaderListener); 83 leadershipService.addListener(leaderListener);
87 - clusterService.addListener(clusterListener);
88 84
89 listenerRegistry = new ListenerRegistry<>(); 85 listenerRegistry = new ListenerRegistry<>();
90 eventDispatcher.addSink(IntentPartitionEvent.class, listenerRegistry); 86 eventDispatcher.addSink(IntentPartitionEvent.class, listenerRegistry);
...@@ -103,7 +99,6 @@ public class IntentPartitionManager implements IntentPartitionService { ...@@ -103,7 +99,6 @@ public class IntentPartitionManager implements IntentPartitionService {
103 99
104 eventDispatcher.removeSink(IntentPartitionEvent.class); 100 eventDispatcher.removeSink(IntentPartitionEvent.class);
105 leadershipService.removeListener(leaderListener); 101 leadershipService.removeListener(leaderListener);
106 - clusterService.removeListener(clusterListener);
107 } 102 }
108 103
109 /** 104 /**
...@@ -180,7 +175,7 @@ public class IntentPartitionManager implements IntentPartitionService { ...@@ -180,7 +175,7 @@ public class IntentPartitionManager implements IntentPartitionService {
180 175
181 List<Leadership> myPartitions = leadershipService.getLeaderBoard().values() 176 List<Leadership> myPartitions = leadershipService.getLeaderBoard().values()
182 .stream() 177 .stream()
183 - .filter(l -> clusterService.getLocalNode().id().equals(l.leader())) 178 + .filter(l -> clusterService.getLocalNode().id().equals(l.leaderNodeId()))
184 .filter(l -> l.topic().startsWith(ELECTION_PREFIX)) 179 .filter(l -> l.topic().startsWith(ELECTION_PREFIX))
185 .collect(Collectors.toList()); 180 .collect(Collectors.toList());
186 181
...@@ -220,24 +215,16 @@ public class IntentPartitionManager implements IntentPartitionService { ...@@ -220,24 +215,16 @@ public class IntentPartitionManager implements IntentPartitionService {
220 public void event(LeadershipEvent event) { 215 public void event(LeadershipEvent event) {
221 Leadership leadership = event.subject(); 216 Leadership leadership = event.subject();
222 217
223 - if (Objects.equals(leadership.leader(), clusterService.getLocalNode().id()) && 218 + if (Objects.equals(leadership.leaderNodeId(), clusterService.getLocalNode().id()) &&
224 leadership.topic().startsWith(ELECTION_PREFIX)) { 219 leadership.topic().startsWith(ELECTION_PREFIX)) {
225 220
226 - // See if we need to let some partitions go
227 - scheduleRebalance(0);
228 -
229 eventDispatcher.post(new IntentPartitionEvent(IntentPartitionEvent.Type.LEADER_CHANGED, 221 eventDispatcher.post(new IntentPartitionEvent(IntentPartitionEvent.Type.LEADER_CHANGED,
230 leadership.topic())); 222 leadership.topic()));
231 } 223 }
232 - }
233 - }
234 224
235 - private final class InternalClusterEventListener implements 225 + if (event.type() == LeadershipEvent.Type.CANDIDATES_CHANGED) {
236 - ClusterEventListener {
237 -
238 - @Override
239 - public void event(ClusterEvent event) {
240 scheduleRebalance(0); 226 scheduleRebalance(0);
241 } 227 }
242 } 228 }
229 + }
243 } 230 }
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
16 package org.onosproject.store.mastership.impl; 16 package org.onosproject.store.mastership.impl;
17 17
18 import static org.onlab.util.Tools.groupedThreads; 18 import static org.onlab.util.Tools.groupedThreads;
19 -import static org.onlab.util.Tools.futureGetOrElse;
20 import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED; 19 import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
21 import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED; 20 import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
22 import static org.slf4j.LoggerFactory.getLogger; 21 import static org.slf4j.LoggerFactory.getLogger;
...@@ -43,6 +42,7 @@ import org.apache.felix.scr.annotations.Service; ...@@ -43,6 +42,7 @@ import org.apache.felix.scr.annotations.Service;
43 import org.onlab.util.KryoNamespace; 42 import org.onlab.util.KryoNamespace;
44 import org.onosproject.cluster.ClusterService; 43 import org.onosproject.cluster.ClusterService;
45 import org.onosproject.cluster.Leadership; 44 import org.onosproject.cluster.Leadership;
45 +import org.onosproject.cluster.LeadershipAdminService;
46 import org.onosproject.cluster.LeadershipEvent; 46 import org.onosproject.cluster.LeadershipEvent;
47 import org.onosproject.cluster.LeadershipEventListener; 47 import org.onosproject.cluster.LeadershipEventListener;
48 import org.onosproject.cluster.LeadershipService; 48 import org.onosproject.cluster.LeadershipService;
...@@ -63,9 +63,9 @@ import org.onosproject.store.serializers.StoreSerializer; ...@@ -63,9 +63,9 @@ import org.onosproject.store.serializers.StoreSerializer;
63 import org.slf4j.Logger; 63 import org.slf4j.Logger;
64 64
65 import com.google.common.base.Objects; 65 import com.google.common.base.Objects;
66 +import com.google.common.collect.ImmutableList;
66 import com.google.common.collect.Lists; 67 import com.google.common.collect.Lists;
67 import com.google.common.collect.Maps; 68 import com.google.common.collect.Maps;
68 -import com.google.common.collect.Sets;
69 69
70 /** 70 /**
71 * Implementation of the MastershipStore on top of Leadership Service. 71 * Implementation of the MastershipStore on top of Leadership Service.
...@@ -82,18 +82,18 @@ public class ConsistentDeviceMastershipStore ...@@ -82,18 +82,18 @@ public class ConsistentDeviceMastershipStore
82 protected LeadershipService leadershipService; 82 protected LeadershipService leadershipService;
83 83
84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 + protected LeadershipAdminService leadershipAdminService;
86 +
87 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 protected ClusterService clusterService; 88 protected ClusterService clusterService;
86 89
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected ClusterCommunicationService clusterCommunicator; 91 protected ClusterCommunicationService clusterCommunicator;
89 92
90 private NodeId localNodeId; 93 private NodeId localNodeId;
91 - private final Set<DeviceId> connectedDevices = Sets.newHashSet();
92 94
93 private static final MessageSubject ROLE_RELINQUISH_SUBJECT = 95 private static final MessageSubject ROLE_RELINQUISH_SUBJECT =
94 new MessageSubject("mastership-store-device-role-relinquish"); 96 new MessageSubject("mastership-store-device-role-relinquish");
95 - private static final MessageSubject TRANSITION_FROM_MASTER_TO_STANDBY_SUBJECT =
96 - new MessageSubject("mastership-store-device-mastership-relinquish");
97 97
98 private static final Pattern DEVICE_MASTERSHIP_TOPIC_PATTERN = 98 private static final Pattern DEVICE_MASTERSHIP_TOPIC_PATTERN =
99 Pattern.compile("device:(.*)"); 99 Pattern.compile("device:(.*)");
...@@ -132,11 +132,6 @@ public class ConsistentDeviceMastershipStore ...@@ -132,11 +132,6 @@ public class ConsistentDeviceMastershipStore
132 this::relinquishLocalRole, 132 this::relinquishLocalRole,
133 SERIALIZER::encode, 133 SERIALIZER::encode,
134 messageHandlingExecutor); 134 messageHandlingExecutor);
135 - clusterCommunicator.addSubscriber(TRANSITION_FROM_MASTER_TO_STANDBY_SUBJECT,
136 - SERIALIZER::decode,
137 - this::transitionFromMasterToStandby,
138 - SERIALIZER::encode,
139 - messageHandlingExecutor);
140 localNodeId = clusterService.getLocalNode().id(); 135 localNodeId = clusterService.getLocalNode().id();
141 leadershipService.addListener(leadershipEventListener); 136 leadershipService.addListener(leadershipEventListener);
142 137
...@@ -146,7 +141,6 @@ public class ConsistentDeviceMastershipStore ...@@ -146,7 +141,6 @@ public class ConsistentDeviceMastershipStore
146 @Deactivate 141 @Deactivate
147 public void deactivate() { 142 public void deactivate() {
148 clusterCommunicator.removeSubscriber(ROLE_RELINQUISH_SUBJECT); 143 clusterCommunicator.removeSubscriber(ROLE_RELINQUISH_SUBJECT);
149 - clusterCommunicator.removeSubscriber(TRANSITION_FROM_MASTER_TO_STANDBY_SUBJECT);
150 messageHandlingExecutor.shutdown(); 144 messageHandlingExecutor.shutdown();
151 transferExecutor.shutdown(); 145 transferExecutor.shutdown();
152 leadershipService.removeListener(leadershipEventListener); 146 leadershipService.removeListener(leadershipEventListener);
...@@ -159,12 +153,9 @@ public class ConsistentDeviceMastershipStore ...@@ -159,12 +153,9 @@ public class ConsistentDeviceMastershipStore
159 checkArgument(deviceId != null, DEVICE_ID_NULL); 153 checkArgument(deviceId != null, DEVICE_ID_NULL);
160 154
161 String leadershipTopic = createDeviceMastershipTopic(deviceId); 155 String leadershipTopic = createDeviceMastershipTopic(deviceId);
162 - connectedDevices.add(deviceId); 156 + Leadership leadership = leadershipService.runForLeadership(leadershipTopic);
163 - return leadershipService.runForLeadership(leadershipTopic) 157 + return CompletableFuture.completedFuture(localNodeId.equals(leadership.leaderNodeId())
164 - .thenApply(leadership -> { 158 + ? MastershipRole.MASTER : MastershipRole.STANDBY);
165 - return Objects.equal(localNodeId, leadership.leader())
166 - ? MastershipRole.MASTER : MastershipRole.STANDBY;
167 - });
168 } 159 }
169 160
170 @Override 161 @Override
...@@ -173,20 +164,19 @@ public class ConsistentDeviceMastershipStore ...@@ -173,20 +164,19 @@ public class ConsistentDeviceMastershipStore
173 checkArgument(deviceId != null, DEVICE_ID_NULL); 164 checkArgument(deviceId != null, DEVICE_ID_NULL);
174 165
175 String leadershipTopic = createDeviceMastershipTopic(deviceId); 166 String leadershipTopic = createDeviceMastershipTopic(deviceId);
176 - NodeId leader = leadershipService.getLeader(leadershipTopic); 167 + Leadership leadership = leadershipService.getLeadership(leadershipTopic);
177 - if (Objects.equal(nodeId, leader)) { 168 + NodeId leader = leadership == null ? null : leadership.leaderNodeId();
178 - return MastershipRole.MASTER; 169 + List<NodeId> candidates = leadership == null ?
179 - } 170 + ImmutableList.of() : ImmutableList.copyOf(leadership.candidates());
180 - return leadershipService.getCandidates(leadershipTopic).contains(nodeId) ? 171 + return Objects.equal(nodeId, leader) ?
181 - MastershipRole.STANDBY : MastershipRole.NONE; 172 + MastershipRole.MASTER : candidates.contains(nodeId) ? MastershipRole.STANDBY : MastershipRole.NONE;
182 } 173 }
183 174
184 @Override 175 @Override
185 public NodeId getMaster(DeviceId deviceId) { 176 public NodeId getMaster(DeviceId deviceId) {
186 checkArgument(deviceId != null, DEVICE_ID_NULL); 177 checkArgument(deviceId != null, DEVICE_ID_NULL);
187 178
188 - String leadershipTopic = createDeviceMastershipTopic(deviceId); 179 + return leadershipService.getLeader(createDeviceMastershipTopic(deviceId));
189 - return leadershipService.getLeader(leadershipTopic);
190 } 180 }
191 181
192 @Override 182 @Override
...@@ -194,8 +184,7 @@ public class ConsistentDeviceMastershipStore ...@@ -194,8 +184,7 @@ public class ConsistentDeviceMastershipStore
194 checkArgument(deviceId != null, DEVICE_ID_NULL); 184 checkArgument(deviceId != null, DEVICE_ID_NULL);
195 185
196 Map<NodeId, MastershipRole> roles = Maps.newHashMap(); 186 Map<NodeId, MastershipRole> roles = Maps.newHashMap();
197 - clusterService 187 + clusterService.getNodes()
198 - .getNodes()
199 .forEach((node) -> roles.put(node.id(), getRole(node.id(), deviceId))); 188 .forEach((node) -> roles.put(node.id(), getRole(node.id(), deviceId)));
200 189
201 NodeId master = null; 190 NodeId master = null;
...@@ -233,30 +222,10 @@ public class ConsistentDeviceMastershipStore ...@@ -233,30 +222,10 @@ public class ConsistentDeviceMastershipStore
233 checkArgument(nodeId != null, NODE_ID_NULL); 222 checkArgument(nodeId != null, NODE_ID_NULL);
234 checkArgument(deviceId != null, DEVICE_ID_NULL); 223 checkArgument(deviceId != null, DEVICE_ID_NULL);
235 224
236 - NodeId currentMaster = getMaster(deviceId);
237 - if (nodeId.equals(currentMaster)) {
238 - return CompletableFuture.completedFuture(null);
239 - } else {
240 String leadershipTopic = createDeviceMastershipTopic(deviceId); 225 String leadershipTopic = createDeviceMastershipTopic(deviceId);
241 - List<NodeId> candidates = leadershipService.getCandidates(leadershipTopic); 226 + if (leadershipAdminService.promoteToTopOfCandidateList(leadershipTopic, nodeId)) {
242 - if (candidates.isEmpty()) { 227 + transferExecutor.schedule(() -> leadershipAdminService.transferLeadership(leadershipTopic, nodeId),
243 - return CompletableFuture.completedFuture(null); 228 + WAIT_BEFORE_MASTERSHIP_HANDOFF_MILLIS, TimeUnit.MILLISECONDS);
244 - }
245 - if (leadershipService.makeTopCandidate(leadershipTopic, nodeId)) {
246 - CompletableFuture<MastershipEvent> result = new CompletableFuture<>();
247 - // There is brief wait before we step down from mastership.
248 - // This is to ensure any work that happens when standby preference
249 - // order changes can complete. For example: flow entries need to be backed
250 - // to the new top standby (ONOS-1883)
251 - // FIXME: This potentially introduces a race-condition.
252 - // Right now role changes are only forced via CLI.
253 - transferExecutor.schedule(() -> {
254 - result.complete(transitionFromMasterToStandby(deviceId));
255 - }, WAIT_BEFORE_MASTERSHIP_HANDOFF_MILLIS, TimeUnit.MILLISECONDS);
256 - return result;
257 - } else {
258 - log.warn("Failed to promote {} to mastership for {}", nodeId, deviceId);
259 - }
260 } 229 }
261 return CompletableFuture.completedFuture(null); 230 return CompletableFuture.completedFuture(null);
262 } 231 }
...@@ -267,7 +236,7 @@ public class ConsistentDeviceMastershipStore ...@@ -267,7 +236,7 @@ public class ConsistentDeviceMastershipStore
267 236
268 String leadershipTopic = createDeviceMastershipTopic(deviceId); 237 String leadershipTopic = createDeviceMastershipTopic(deviceId);
269 Leadership leadership = leadershipService.getLeadership(leadershipTopic); 238 Leadership leadership = leadershipService.getLeadership(leadershipTopic);
270 - return leadership != null ? MastershipTerm.of(leadership.leader(), leadership.epoch()) : null; 239 + return leadership != null ? MastershipTerm.of(leadership.leaderNodeId(), leadership.leader().term()) : null;
271 } 240 }
272 241
273 @Override 242 @Override
...@@ -318,71 +287,44 @@ public class ConsistentDeviceMastershipStore ...@@ -318,71 +287,44 @@ public class ConsistentDeviceMastershipStore
318 private CompletableFuture<MastershipEvent> relinquishLocalRole(DeviceId deviceId) { 287 private CompletableFuture<MastershipEvent> relinquishLocalRole(DeviceId deviceId) {
319 checkArgument(deviceId != null, DEVICE_ID_NULL); 288 checkArgument(deviceId != null, DEVICE_ID_NULL);
320 289
321 - // Check if this node is can be managed by this node.
322 - if (!connectedDevices.contains(deviceId)) {
323 - return CompletableFuture.completedFuture(null);
324 - }
325 -
326 String leadershipTopic = createDeviceMastershipTopic(deviceId); 290 String leadershipTopic = createDeviceMastershipTopic(deviceId);
327 - NodeId currentLeader = leadershipService.getLeader(leadershipTopic); 291 + if (!leadershipService.getCandidates(leadershipTopic).contains(localNodeId)) {
328 - 292 + return CompletableFuture.completedFuture(null);
329 - MastershipEvent.Type eventType = Objects.equal(currentLeader, localNodeId)
330 - ? MastershipEvent.Type.MASTER_CHANGED
331 - : MastershipEvent.Type.BACKUPS_CHANGED;
332 -
333 - connectedDevices.remove(deviceId);
334 - return leadershipService.withdraw(leadershipTopic)
335 - .thenApply(v -> new MastershipEvent(eventType, deviceId, getNodes(deviceId)));
336 } 293 }
337 - 294 + MastershipEvent.Type eventType = localNodeId.equals(leadershipService.getLeader(leadershipTopic)) ?
338 - private MastershipEvent transitionFromMasterToStandby(DeviceId deviceId) { 295 + MastershipEvent.Type.MASTER_CHANGED : MastershipEvent.Type.BACKUPS_CHANGED;
339 - checkArgument(deviceId != null, DEVICE_ID_NULL); 296 + leadershipService.withdraw(leadershipTopic);
340 - 297 + return CompletableFuture.completedFuture(new MastershipEvent(eventType, deviceId, getNodes(deviceId)));
341 - NodeId currentMaster = getMaster(deviceId);
342 - if (currentMaster == null) {
343 - return null;
344 } 298 }
345 299
346 - if (!currentMaster.equals(localNodeId)) { 300 + @Override
347 - log.info("Forwarding request to relinquish " 301 + public void relinquishAllRole(NodeId nodeId) {
348 - + "mastership for device {} to {}", deviceId, currentMaster); 302 + // Noop. LeadershipService already takes care of detecting and purging stale locks.
349 - return futureGetOrElse(clusterCommunicator.sendAndReceive(
350 - deviceId,
351 - TRANSITION_FROM_MASTER_TO_STANDBY_SUBJECT,
352 - SERIALIZER::encode,
353 - SERIALIZER::decode,
354 - currentMaster), null);
355 } 303 }
356 304
357 - return leadershipService.stepdown(createDeviceMastershipTopic(deviceId)) 305 + private class InternalDeviceMastershipEventListener implements LeadershipEventListener {
358 - ? new MastershipEvent(MastershipEvent.Type.MASTER_CHANGED, deviceId, getNodes(deviceId)) : null;
359 - }
360 306
361 @Override 307 @Override
362 - public void relinquishAllRole(NodeId nodeId) { 308 + public boolean isRelevant(LeadershipEvent event) {
363 - // Noop. LeadershipService already takes care of detecting and purging deadlocks. 309 + Leadership leadership = event.subject();
310 + return isDeviceMastershipTopic(leadership.topic());
364 } 311 }
365 312
366 - private class InternalDeviceMastershipEventListener implements LeadershipEventListener {
367 @Override 313 @Override
368 public void event(LeadershipEvent event) { 314 public void event(LeadershipEvent event) {
369 Leadership leadership = event.subject(); 315 Leadership leadership = event.subject();
370 - if (!isDeviceMastershipTopic(leadership.topic())) {
371 - return;
372 - }
373 DeviceId deviceId = extractDeviceIdFromTopic(leadership.topic()); 316 DeviceId deviceId = extractDeviceIdFromTopic(leadership.topic());
317 + RoleInfo roleInfo = getNodes(deviceId);
374 switch (event.type()) { 318 switch (event.type()) {
375 - case LEADER_ELECTED: 319 + case LEADER_AND_CANDIDATES_CHANGED:
376 - notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId))); 320 + notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId, roleInfo));
321 + notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, roleInfo));
377 break; 322 break;
378 - case LEADER_REELECTED: 323 + case LEADER_CHANGED:
379 - // There is no concept of leader re-election in the new distributed leadership manager. 324 + notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, roleInfo));
380 - throw new IllegalStateException("Unexpected event type");
381 - case LEADER_BOOTED:
382 - notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
383 break; 325 break;
384 case CANDIDATES_CHANGED: 326 case CANDIDATES_CHANGED:
385 - notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId, getNodes(deviceId))); 327 + notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId, roleInfo));
386 break; 328 break;
387 default: 329 default:
388 return; 330 return;
...@@ -407,5 +349,4 @@ public class ConsistentDeviceMastershipStore ...@@ -407,5 +349,4 @@ public class ConsistentDeviceMastershipStore
407 Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic); 349 Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
408 return m.matches(); 350 return m.matches();
409 } 351 }
410 -
411 } 352 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,6 +22,7 @@ import org.onlab.packet.IpAddress; ...@@ -22,6 +22,7 @@ import org.onlab.packet.IpAddress;
22 import org.onosproject.cluster.ClusterServiceAdapter; 22 import org.onosproject.cluster.ClusterServiceAdapter;
23 import org.onosproject.cluster.ControllerNode; 23 import org.onosproject.cluster.ControllerNode;
24 import org.onosproject.cluster.DefaultControllerNode; 24 import org.onosproject.cluster.DefaultControllerNode;
25 +import org.onosproject.cluster.Leader;
25 import org.onosproject.cluster.Leadership; 26 import org.onosproject.cluster.Leadership;
26 import org.onosproject.cluster.LeadershipEvent; 27 import org.onosproject.cluster.LeadershipEvent;
27 import org.onosproject.cluster.LeadershipEventListener; 28 import org.onosproject.cluster.LeadershipEventListener;
...@@ -31,13 +32,12 @@ import org.onosproject.cluster.NodeId; ...@@ -31,13 +32,12 @@ import org.onosproject.cluster.NodeId;
31 import org.onosproject.common.event.impl.TestEventDispatcher; 32 import org.onosproject.common.event.impl.TestEventDispatcher;
32 import org.onosproject.net.intent.Key; 33 import org.onosproject.net.intent.Key;
33 34
35 +import java.util.Arrays;
34 import java.util.HashMap; 36 import java.util.HashMap;
35 import java.util.HashSet; 37 import java.util.HashSet;
36 import java.util.Map; 38 import java.util.Map;
37 import java.util.Objects; 39 import java.util.Objects;
38 import java.util.Set; 40 import java.util.Set;
39 -import java.util.concurrent.CompletableFuture;
40 -
41 import static junit.framework.TestCase.assertFalse; 41 import static junit.framework.TestCase.assertFalse;
42 import static org.easymock.EasyMock.anyObject; 42 import static org.easymock.EasyMock.anyObject;
43 import static org.easymock.EasyMock.anyString; 43 import static org.easymock.EasyMock.anyString;
...@@ -55,9 +55,10 @@ import static org.junit.Assert.assertTrue; ...@@ -55,9 +55,10 @@ import static org.junit.Assert.assertTrue;
55 public class IntentPartitionManagerTest { 55 public class IntentPartitionManagerTest {
56 56
57 private final LeadershipEvent event 57 private final LeadershipEvent event
58 - = new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, 58 + = new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED,
59 new Leadership(ELECTION_PREFIX + "0", 59 new Leadership(ELECTION_PREFIX + "0",
60 - MY_NODE_ID, 0, 0)); 60 + new Leader(MY_NODE_ID, 0, 0),
61 + Arrays.asList(MY_NODE_ID, OTHER_NODE_ID)));
61 62
62 private static final NodeId MY_NODE_ID = new NodeId("local"); 63 private static final NodeId MY_NODE_ID = new NodeId("local");
63 private static final NodeId OTHER_NODE_ID = new NodeId("other"); 64 private static final NodeId OTHER_NODE_ID = new NodeId("other");
...@@ -78,7 +79,7 @@ public class IntentPartitionManagerTest { ...@@ -78,7 +79,7 @@ public class IntentPartitionManagerTest {
78 expectLastCall().andDelegateTo(new TestLeadershipService()); 79 expectLastCall().andDelegateTo(new TestLeadershipService());
79 for (int i = 0; i < IntentPartitionManager.NUM_PARTITIONS; i++) { 80 for (int i = 0; i < IntentPartitionManager.NUM_PARTITIONS; i++) {
80 expect(leadershipService.runForLeadership(ELECTION_PREFIX + i)) 81 expect(leadershipService.runForLeadership(ELECTION_PREFIX + i))
81 - .andReturn(CompletableFuture.completedFuture(null)) 82 + .andReturn(null)
82 .times(1); 83 .times(1);
83 } 84 }
84 85
...@@ -105,7 +106,9 @@ public class IntentPartitionManagerTest { ...@@ -105,7 +106,9 @@ public class IntentPartitionManagerTest {
105 expect(leadershipService.getLeader(ELECTION_PREFIX + i)) 106 expect(leadershipService.getLeader(ELECTION_PREFIX + i))
106 .andReturn(MY_NODE_ID).anyTimes(); 107 .andReturn(MY_NODE_ID).anyTimes();
107 leaderBoard.put(ELECTION_PREFIX + i, 108 leaderBoard.put(ELECTION_PREFIX + i,
108 - new Leadership(ELECTION_PREFIX + i, MY_NODE_ID, 0, 0)); 109 + new Leadership(ELECTION_PREFIX + i,
110 + new Leader(MY_NODE_ID, 0, 0),
111 + Arrays.asList(MY_NODE_ID)));
109 } 112 }
110 113
111 for (int i = numMine; i < IntentPartitionManager.NUM_PARTITIONS; i++) { 114 for (int i = numMine; i < IntentPartitionManager.NUM_PARTITIONS; i++) {
...@@ -113,7 +116,9 @@ public class IntentPartitionManagerTest { ...@@ -113,7 +116,9 @@ public class IntentPartitionManagerTest {
113 .andReturn(OTHER_NODE_ID).anyTimes(); 116 .andReturn(OTHER_NODE_ID).anyTimes();
114 117
115 leaderBoard.put(ELECTION_PREFIX + i, 118 leaderBoard.put(ELECTION_PREFIX + i,
116 - new Leadership(ELECTION_PREFIX + i, OTHER_NODE_ID, 0, 0)); 119 + new Leadership(ELECTION_PREFIX + i,
120 + new Leader(OTHER_NODE_ID, 0, 0),
121 + Arrays.asList(OTHER_NODE_ID)));
117 } 122 }
118 123
119 expect(leadershipService.getLeaderBoard()).andReturn(leaderBoard).anyTimes(); 124 expect(leadershipService.getLeaderBoard()).andReturn(leaderBoard).anyTimes();
...@@ -131,7 +136,7 @@ public class IntentPartitionManagerTest { ...@@ -131,7 +136,7 @@ public class IntentPartitionManagerTest {
131 136
132 for (int i = 0; i < IntentPartitionManager.NUM_PARTITIONS; i++) { 137 for (int i = 0; i < IntentPartitionManager.NUM_PARTITIONS; i++) {
133 expect(leadershipService.runForLeadership(ELECTION_PREFIX + i)) 138 expect(leadershipService.runForLeadership(ELECTION_PREFIX + i))
134 - .andReturn(CompletableFuture.completedFuture(null)) 139 + .andReturn(null)
135 .times(1); 140 .times(1);
136 } 141 }
137 142
...@@ -200,9 +205,8 @@ public class IntentPartitionManagerTest { ...@@ -200,9 +205,8 @@ public class IntentPartitionManagerTest {
200 // We have all the partitions so we'll need to relinquish some 205 // We have all the partitions so we'll need to relinquish some
201 setUpLeadershipService(IntentPartitionManager.NUM_PARTITIONS); 206 setUpLeadershipService(IntentPartitionManager.NUM_PARTITIONS);
202 207
203 - expect(leadershipService.withdraw(anyString())) 208 + leadershipService.withdraw(anyString());
204 - .andReturn(CompletableFuture.completedFuture(null)) 209 + expectLastCall().times(7);
205 - .times(7);
206 210
207 replay(leadershipService); 211 replay(leadershipService);
208 212
......
1 -/*
2 - * Copyright 2015 Open Networking Laboratory
3 - *
4 - * Licensed under the Apache License, Version 2.0 (the "License");
5 - * you may not use this file except in compliance with the License.
6 - * You may obtain a copy of the License at
7 - *
8 - * http://www.apache.org/licenses/LICENSE-2.0
9 - *
10 - * Unless required by applicable law or agreed to in writing, software
11 - * distributed under the License is distributed on an "AS IS" BASIS,
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 - * See the License for the specific language governing permissions and
14 - * limitations under the License.
15 - */
16 -package org.onosproject.store.primitives.impl;
17 -
18 -import com.google.common.collect.ImmutableList;
19 -import com.google.common.collect.ImmutableMap;
20 -import com.google.common.collect.Iterables;
21 -import com.google.common.collect.Lists;
22 -import com.google.common.collect.MapDifference;
23 -import com.google.common.collect.Maps;
24 -import com.google.common.collect.Sets;
25 -
26 -import org.apache.commons.lang.math.RandomUtils;
27 -import org.apache.felix.scr.annotations.Activate;
28 -import org.apache.felix.scr.annotations.Component;
29 -import org.apache.felix.scr.annotations.Deactivate;
30 -import org.apache.felix.scr.annotations.Reference;
31 -import org.apache.felix.scr.annotations.ReferenceCardinality;
32 -import org.apache.felix.scr.annotations.Service;
33 -import org.onosproject.cluster.ClusterEvent;
34 -import org.onosproject.cluster.ClusterEvent.Type;
35 -import org.onosproject.cluster.ClusterEventListener;
36 -import org.onosproject.cluster.ClusterService;
37 -import org.onosproject.cluster.Leadership;
38 -import org.onosproject.cluster.LeadershipEvent;
39 -import org.onosproject.cluster.LeadershipEventListener;
40 -import org.onosproject.cluster.LeadershipService;
41 -import org.onosproject.cluster.NodeId;
42 -import org.onosproject.event.ListenerRegistry;
43 -import org.onosproject.event.EventDeliveryService;
44 -import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
45 -import org.onosproject.store.serializers.KryoNamespaces;
46 -import org.onosproject.store.service.ConsistentMap;
47 -import org.onosproject.store.service.ConsistentMapException;
48 -import org.onosproject.store.service.MapEvent;
49 -import org.onosproject.store.service.Serializer;
50 -import org.onosproject.store.service.StorageService;
51 -import org.onosproject.store.service.Versioned;
52 -import org.slf4j.Logger;
53 -
54 -import java.util.ArrayList;
55 -import java.util.Collections;
56 -import java.util.Map;
57 -import java.util.Map.Entry;
58 -import java.util.Objects;
59 -import java.util.Set;
60 -import java.util.List;
61 -import java.util.concurrent.CancellationException;
62 -import java.util.concurrent.CompletableFuture;
63 -import java.util.concurrent.Executors;
64 -import java.util.concurrent.ScheduledExecutorService;
65 -import java.util.concurrent.TimeUnit;
66 -import java.util.concurrent.atomic.AtomicBoolean;
67 -import java.util.stream.Collectors;
68 -
69 -import static com.google.common.base.Preconditions.checkArgument;
70 -import static org.onlab.util.Tools.groupedThreads;
71 -import static org.slf4j.LoggerFactory.getLogger;
72 -import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
73 -import static org.onosproject.cluster.ControllerNode.State.INACTIVE;
74 -
75 -/**
76 - * Distributed Lock Manager implemented on top of ConsistentMap.
77 - * <p>
78 - * This implementation makes use of ClusterService's failure
79 - * detection capabilities to detect and purge stale locks.
80 - * TODO: Ensure lock safety and liveness.
81 - */
82 -@Component(immediate = true, enabled = true)
83 -@Service
84 -public class DistributedLeadershipManager implements LeadershipService {
85 -
86 - @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 - protected StorageService storageService;
88 -
89 - @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 - protected ClusterService clusterService;
91 -
92 - @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 - protected ClusterCommunicationService clusterCommunicator;
94 -
95 - @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 - protected EventDeliveryService eventDispatcher;
97 -
98 - private final Logger log = getLogger(getClass());
99 -
100 - private ScheduledExecutorService electionRunner;
101 - private ScheduledExecutorService lockExecutor;
102 - private ScheduledExecutorService staleLeadershipPurgeExecutor;
103 - private ScheduledExecutorService leadershipRefresher;
104 -
105 - // leader for each topic
106 - private ConsistentMap<String, NodeId> leaderMap;
107 - // list of candidates (includes chosen leader) for each topic
108 - private ConsistentMap<String, List<NodeId>> candidateMap;
109 -
110 - private ListenerRegistry<LeadershipEvent, LeadershipEventListener> listenerRegistry;
111 -
112 - // cached copy of leaderMap
113 - // Note: Map value, Leadership, does not contain proper candidates info
114 - private final Map<String, Leadership> leaderBoard = Maps.newConcurrentMap();
115 - // cached copy of candidateMap
116 - // Note: Map value, Leadership, does not contain proper leader info
117 - private final Map<String, Leadership> candidateBoard = Maps.newConcurrentMap();
118 -
119 - private final ClusterEventListener clusterEventListener = new InternalClusterEventListener();
120 -
121 - private NodeId localNodeId;
122 - private Set<String> activeTopics = Sets.newConcurrentHashSet();
123 - private Map<String, CompletableFuture<Leadership>> pendingFutures = Maps.newConcurrentMap();
124 -
125 - // The actual delay is randomly chosen from the interval [0, WAIT_BEFORE_RETRY_MILLIS)
126 - private static final int WAIT_BEFORE_RETRY_MILLIS = 150;
127 - private static final int DELAY_BETWEEN_LEADER_LOCK_ATTEMPTS_SEC = 2;
128 - private static final int LEADERSHIP_REFRESH_INTERVAL_SEC = 2;
129 - private static final int DELAY_BETWEEN_STALE_LEADERSHIP_PURGE_ATTEMPTS_SEC = 2;
130 -
131 - private final AtomicBoolean staleLeadershipPurgeScheduled = new AtomicBoolean(false);
132 -
133 - private static final Serializer SERIALIZER = Serializer.using(KryoNamespaces.API);
134 -
135 - @Activate
136 - public void activate() {
137 - leaderMap = storageService.<String, NodeId>consistentMapBuilder()
138 - .withName("onos-topic-leaders")
139 - .withSerializer(SERIALIZER)
140 - .withPartitionsDisabled().build();
141 - candidateMap = storageService.<String, List<NodeId>>consistentMapBuilder()
142 - .withName("onos-topic-candidates")
143 - .withSerializer(SERIALIZER)
144 - .withPartitionsDisabled().build();
145 -
146 - leaderMap.addListener(event -> {
147 - log.debug("Received {}", event);
148 - LeadershipEvent.Type leadershipEventType = null;
149 - if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
150 - leadershipEventType = LeadershipEvent.Type.LEADER_ELECTED;
151 - } else if (event.type() == MapEvent.Type.REMOVE) {
152 - leadershipEventType = LeadershipEvent.Type.LEADER_BOOTED;
153 - }
154 - onLeadershipEvent(new LeadershipEvent(
155 - leadershipEventType,
156 - new Leadership(event.key(),
157 - event.value().value(),
158 - event.value().version(),
159 - event.value().creationTime())));
160 - });
161 -
162 - candidateMap.addListener(event -> {
163 - log.debug("Received {}", event);
164 - if (event.type() != MapEvent.Type.INSERT && event.type() != MapEvent.Type.UPDATE) {
165 - log.error("Entries must not be removed from candidate map");
166 - return;
167 - }
168 - onLeadershipEvent(new LeadershipEvent(
169 - LeadershipEvent.Type.CANDIDATES_CHANGED,
170 - new Leadership(event.key(),
171 - event.value().value(),
172 - event.value().version(),
173 - event.value().creationTime())));
174 - });
175 -
176 - localNodeId = clusterService.getLocalNode().id();
177 -
178 - electionRunner = Executors.newSingleThreadScheduledExecutor(
179 - groupedThreads("onos/store/leadership", "election-runner"));
180 - lockExecutor = Executors.newScheduledThreadPool(
181 - 4, groupedThreads("onos/store/leadership", "election-thread-%d"));
182 - staleLeadershipPurgeExecutor = Executors.newSingleThreadScheduledExecutor(
183 - groupedThreads("onos/store/leadership", "stale-leadership-evictor"));
184 - leadershipRefresher = Executors.newSingleThreadScheduledExecutor(
185 - groupedThreads("onos/store/leadership", "refresh-thread"));
186 -
187 - clusterService.addListener(clusterEventListener);
188 -
189 - electionRunner.scheduleWithFixedDelay(
190 - this::electLeaders, 0, DELAY_BETWEEN_LEADER_LOCK_ATTEMPTS_SEC, TimeUnit.SECONDS);
191 -
192 - leadershipRefresher.scheduleWithFixedDelay(
193 - this::refreshLeaderBoard, 0, LEADERSHIP_REFRESH_INTERVAL_SEC, TimeUnit.SECONDS);
194 -
195 - listenerRegistry = new ListenerRegistry<>();
196 - eventDispatcher.addSink(LeadershipEvent.class, listenerRegistry);
197 -
198 - log.info("Started");
199 - }
200 -
201 - @Deactivate
202 - public void deactivate() {
203 - if (clusterService.getNodes().size() > 1) {
204 - // FIXME: Determine why this takes ~50 seconds to shutdown on a single node!
205 - leaderBoard.forEach((topic, leadership) -> {
206 - if (localNodeId.equals(leadership.leader())) {
207 - withdraw(topic);
208 - }
209 - });
210 - }
211 -
212 - clusterService.removeListener(clusterEventListener);
213 - eventDispatcher.removeSink(LeadershipEvent.class);
214 -
215 - electionRunner.shutdown();
216 - lockExecutor.shutdown();
217 - staleLeadershipPurgeExecutor.shutdown();
218 - leadershipRefresher.shutdown();
219 -
220 - log.info("Stopped");
221 - }
222 -
223 - @Override
224 - public Map<String, Leadership> getLeaderBoard() {
225 - return ImmutableMap.copyOf(leaderBoard);
226 - }
227 -
228 - @Override
229 - public Map<String, List<NodeId>> getCandidates() {
230 - return Maps.toMap(candidateBoard.keySet(), this::getCandidates);
231 - }
232 -
233 - @Override
234 - public List<NodeId> getCandidates(String path) {
235 - Leadership current = candidateBoard.get(path);
236 - return current == null ? ImmutableList.of() : ImmutableList.copyOf(current.candidates());
237 - }
238 -
239 - @Override
240 - public NodeId getLeader(String path) {
241 - Leadership leadership = leaderBoard.get(path);
242 - return leadership != null ? leadership.leader() : null;
243 - }
244 -
245 - @Override
246 - public Leadership getLeadership(String path) {
247 - checkArgument(path != null);
248 - return leaderBoard.get(path);
249 - }
250 -
251 - @Override
252 - public Set<String> ownedTopics(NodeId nodeId) {
253 - checkArgument(nodeId != null);
254 - return leaderBoard.entrySet()
255 - .stream()
256 - .filter(entry -> nodeId.equals(entry.getValue().leader()))
257 - .map(Entry::getKey)
258 - .collect(Collectors.toSet());
259 - }
260 -
261 - @Override
262 - public CompletableFuture<Leadership> runForLeadership(String path) {
263 - log.debug("Running for leadership for topic: {}", path);
264 - CompletableFuture<Leadership> resultFuture = new CompletableFuture<>();
265 - doRunForLeadership(path, resultFuture);
266 - return resultFuture;
267 - }
268 -
269 - private void doRunForLeadership(String path, CompletableFuture<Leadership> future) {
270 - try {
271 - Versioned<List<NodeId>> candidates = candidateMap.computeIf(path,
272 - currentList -> currentList == null || !currentList.contains(localNodeId),
273 - (topic, currentList) -> {
274 - if (currentList == null) {
275 - return ImmutableList.of(localNodeId);
276 - } else {
277 - List<NodeId> newList = Lists.newLinkedList();
278 - newList.addAll(currentList);
279 - newList.add(localNodeId);
280 - return newList;
281 - }
282 - });
283 - log.debug("In the leadership race for topic {} with candidates {}", path, candidates);
284 - activeTopics.add(path);
285 - Leadership leadership = electLeader(path, candidates.value());
286 - if (leadership == null) {
287 - pendingFutures.put(path, future);
288 - } else {
289 - future.complete(leadership);
290 - }
291 - } catch (ConsistentMapException e) {
292 - log.debug("Failed to enter topic leader race for {}. Retrying.", path, e);
293 - rerunForLeadership(path, future);
294 - }
295 - }
296 -
297 - @Override
298 - public CompletableFuture<Void> withdraw(String path) {
299 - activeTopics.remove(path);
300 - CompletableFuture<Void> resultFuture = new CompletableFuture<>();
301 - doWithdraw(path, resultFuture);
302 - return resultFuture;
303 - }
304 -
305 -
306 - private void doWithdraw(String path, CompletableFuture<Void> future) {
307 - if (activeTopics.contains(path)) {
308 - future.completeExceptionally(new CancellationException(String.format("%s is now a active topic", path)));
309 - }
310 - try {
311 - leaderMap.computeIf(path,
312 - localNodeId::equals,
313 - (topic, leader) -> null);
314 - candidateMap.computeIf(path,
315 - candidates -> candidates != null && candidates.contains(localNodeId),
316 - (topic, candidates) -> candidates.stream()
317 - .filter(nodeId -> !localNodeId.equals(nodeId))
318 - .collect(Collectors.toList()));
319 - future.complete(null);
320 - } catch (Exception e) {
321 - log.debug("Failed to verify (and clear) any lock this node might be holding for {}", path, e);
322 - retryWithdraw(path, future);
323 - }
324 - }
325 -
326 - @Override
327 - public boolean stepdown(String path) {
328 - if (!activeTopics.contains(path) || !Objects.equals(localNodeId, getLeader(path))) {
329 - return false;
330 - }
331 -
332 - try {
333 - return leaderMap.computeIf(path,
334 - localNodeId::equals,
335 - (topic, leader) -> null) == null;
336 - } catch (Exception e) {
337 - log.warn("Error executing stepdown for {}", path, e);
338 - }
339 - return false;
340 - }
341 -
342 - @Override
343 - public void addListener(LeadershipEventListener listener) {
344 - listenerRegistry.addListener(listener);
345 - }
346 -
347 - @Override
348 - public void removeListener(LeadershipEventListener listener) {
349 - listenerRegistry.removeListener(listener);
350 - }
351 -
352 - @Override
353 - public boolean makeTopCandidate(String path, NodeId nodeId) {
354 - Versioned<List<NodeId>> candidateList = candidateMap.computeIf(path,
355 - candidates -> candidates != null &&
356 - candidates.contains(nodeId) &&
357 - !nodeId.equals(Iterables.getFirst(candidates, null)),
358 - (topic, candidates) -> {
359 - List<NodeId> updatedCandidates = new ArrayList<>(candidates.size());
360 - updatedCandidates.add(nodeId);
361 - candidates.stream().filter(id -> !nodeId.equals(id)).forEach(updatedCandidates::add);
362 - return updatedCandidates;
363 - });
364 - List<NodeId> candidates = candidateList != null ? candidateList.value() : Collections.emptyList();
365 - return candidates.size() > 0 && nodeId.equals(candidates.get(0));
366 - }
367 -
368 - private Leadership electLeader(String path, List<NodeId> candidates) {
369 - Leadership currentLeadership = getLeadership(path);
370 - if (currentLeadership != null) {
371 - return currentLeadership;
372 - } else {
373 - NodeId topCandidate = candidates
374 - .stream()
375 - .filter(n -> clusterService.getState(n) == ACTIVE)
376 - .findFirst()
377 - .orElse(null);
378 - try {
379 - Versioned<NodeId> leader = localNodeId.equals(topCandidate)
380 - ? leaderMap.computeIfAbsent(path, p -> localNodeId) : leaderMap.get(path);
381 - if (leader != null) {
382 - Leadership newLeadership = new Leadership(path,
383 - leader.value(),
384 - leader.version(),
385 - leader.creationTime());
386 - // Since reads only go through the local copy of leader board, we ought to update it
387 - // first before returning from this method.
388 - // This is to ensure a subsequent read will not read a stale value.
389 - onLeadershipEvent(new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, newLeadership));
390 - return newLeadership;
391 - }
392 - } catch (Exception e) {
393 - log.debug("Failed to elect leader for {}", path, e);
394 - }
395 - }
396 - return null;
397 - }
398 -
399 - private void electLeaders() {
400 - try {
401 - candidateMap.entrySet().forEach(entry -> {
402 - String path = entry.getKey();
403 - Versioned<List<NodeId>> candidates = entry.getValue();
404 - // for active topics, check if this node can become a leader (if it isn't already)
405 - if (activeTopics.contains(path)) {
406 - lockExecutor.submit(() -> {
407 - Leadership leadership = electLeader(path, candidates.value());
408 - if (leadership != null) {
409 - CompletableFuture<Leadership> future = pendingFutures.remove(path);
410 - if (future != null) {
411 - future.complete(leadership);
412 - }
413 - }
414 - });
415 - }
416 - // Raise a CANDIDATES_CHANGED event to force refresh local candidate board
417 - // and also to update local listeners.
418 - // Don't worry about duplicate events as they will be suppressed.
419 - onLeadershipEvent(new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED,
420 - new Leadership(path,
421 - candidates.value(),
422 - candidates.version(),
423 - candidates.creationTime())));
424 - });
425 - } catch (Exception e) {
426 - log.debug("Failure electing leaders", e);
427 - }
428 - }
429 -
430 - private void onLeadershipEvent(LeadershipEvent leadershipEvent) {
431 - log.trace("Leadership Event: time = {} type = {} event = {}",
432 - leadershipEvent.time(), leadershipEvent.type(),
433 - leadershipEvent);
434 -
435 - Leadership leadershipUpdate = leadershipEvent.subject();
436 - LeadershipEvent.Type eventType = leadershipEvent.type();
437 - String topic = leadershipUpdate.topic();
438 -
439 - AtomicBoolean updateAccepted = new AtomicBoolean(false);
440 - if (eventType.equals(LeadershipEvent.Type.LEADER_ELECTED)) {
441 - leaderBoard.compute(topic, (k, currentLeadership) -> {
442 - if (currentLeadership == null || currentLeadership.epoch() < leadershipUpdate.epoch()) {
443 - updateAccepted.set(true);
444 - return leadershipUpdate;
445 - }
446 - return currentLeadership;
447 - });
448 - } else if (eventType.equals(LeadershipEvent.Type.LEADER_BOOTED)) {
449 - leaderBoard.compute(topic, (k, currentLeadership) -> {
450 - if (currentLeadership == null || currentLeadership.epoch() <= leadershipUpdate.epoch()) {
451 - updateAccepted.set(true);
452 - // FIXME: Removing entries from leaderboard is not safe and should be visited.
453 - return null;
454 - }
455 - return currentLeadership;
456 - });
457 - } else if (eventType.equals(LeadershipEvent.Type.CANDIDATES_CHANGED)) {
458 - candidateBoard.compute(topic, (k, currentInfo) -> {
459 - if (currentInfo == null || currentInfo.epoch() < leadershipUpdate.epoch()) {
460 - updateAccepted.set(true);
461 - return leadershipUpdate;
462 - }
463 - return currentInfo;
464 - });
465 - } else {
466 - throw new IllegalStateException("Unknown event type.");
467 - }
468 -
469 - if (updateAccepted.get()) {
470 - eventDispatcher.post(leadershipEvent);
471 - }
472 - }
473 -
474 - private void rerunForLeadership(String path, CompletableFuture<Leadership> future) {
475 - lockExecutor.schedule(
476 - () -> doRunForLeadership(path, future),
477 - RandomUtils.nextInt(WAIT_BEFORE_RETRY_MILLIS),
478 - TimeUnit.MILLISECONDS);
479 - }
480 -
481 - private void retryWithdraw(String path, CompletableFuture<Void> future) {
482 - lockExecutor.schedule(
483 - () -> doWithdraw(path, future),
484 - RandomUtils.nextInt(WAIT_BEFORE_RETRY_MILLIS),
485 - TimeUnit.MILLISECONDS);
486 - }
487 -
488 - private void scheduleStaleLeadershipPurge(int afterDelaySec) {
489 - if (staleLeadershipPurgeScheduled.compareAndSet(false, true)) {
490 - staleLeadershipPurgeExecutor.schedule(
491 - this::purgeStaleLeadership,
492 - afterDelaySec,
493 - TimeUnit.SECONDS);
494 - }
495 - }
496 -
497 - /**
498 - * Purges locks held by inactive nodes and evicts inactive nodes from candidacy.
499 - */
500 - private void purgeStaleLeadership() {
501 - AtomicBoolean rerunPurge = new AtomicBoolean(false);
502 - try {
503 - staleLeadershipPurgeScheduled.set(false);
504 - leaderMap.entrySet()
505 - .stream()
506 - .filter(e -> clusterService.getState(e.getValue().value()) == INACTIVE)
507 - .forEach(entry -> {
508 - String path = entry.getKey();
509 - NodeId nodeId = entry.getValue().value();
510 - try {
511 - leaderMap.computeIf(path, nodeId::equals, (topic, leader) -> null);
512 - } catch (Exception e) {
513 - log.debug("Failed to purge stale lock held by {} for {}", nodeId, path, e);
514 - rerunPurge.set(true);
515 - }
516 - });
517 -
518 - candidateMap.entrySet()
519 - .forEach(entry -> {
520 - String path = entry.getKey();
521 - Versioned<List<NodeId>> candidates = entry.getValue();
522 - List<NodeId> candidatesList = candidates != null
523 - ? candidates.value() : Collections.emptyList();
524 - List<NodeId> activeCandidatesList =
525 - candidatesList.stream()
526 - .filter(n -> clusterService.getState(n) == ACTIVE)
527 - .filter(n -> !localNodeId.equals(n) || activeTopics.contains(path))
528 - .collect(Collectors.toList());
529 - if (activeCandidatesList.size() < candidatesList.size()) {
530 - Set<NodeId> removedCandidates =
531 - Sets.difference(Sets.newHashSet(candidatesList),
532 - Sets.newHashSet(activeCandidatesList));
533 - try {
534 - candidateMap.computeIf(path,
535 - c -> c.stream()
536 - .filter(n -> clusterService.getState(n) == INACTIVE)
537 - .count() > 0,
538 - (topic, c) -> c.stream()
539 - .filter(n -> clusterService.getState(n) == ACTIVE)
540 - .filter(n -> !localNodeId.equals(n) ||
541 - activeTopics.contains(path))
542 - .collect(Collectors.toList()));
543 - } catch (Exception e) {
544 - log.debug("Failed to evict inactive candidates {} from "
545 - + "candidate list for {}", removedCandidates, path, e);
546 - rerunPurge.set(true);
547 - }
548 - }
549 - });
550 - } catch (Exception e) {
551 - log.debug("Failure purging state leadership.", e);
552 - rerunPurge.set(true);
553 - }
554 -
555 - if (rerunPurge.get()) {
556 - log.debug("Rescheduling stale leadership purge due to errors encountered in previous run");
557 - scheduleStaleLeadershipPurge(DELAY_BETWEEN_STALE_LEADERSHIP_PURGE_ATTEMPTS_SEC);
558 - }
559 - }
560 -
561 - private void refreshLeaderBoard() {
562 - try {
563 - Map<String, Leadership> newLeaderBoard = Maps.newHashMap();
564 - leaderMap.entrySet().forEach(entry -> {
565 - String path = entry.getKey();
566 - Versioned<NodeId> leader = entry.getValue();
567 - Leadership leadership = new Leadership(path,
568 - leader.value(),
569 - leader.version(),
570 - leader.creationTime());
571 - newLeaderBoard.put(path, leadership);
572 - });
573 -
574 - // first take snapshot of current leader board.
575 - Map<String, Leadership> currentLeaderBoard = ImmutableMap.copyOf(leaderBoard);
576 -
577 - MapDifference<String, Leadership> diff = Maps.difference(currentLeaderBoard, newLeaderBoard);
578 -
579 - // evict stale leaders
580 - diff.entriesOnlyOnLeft().forEach((path, leadership) -> {
581 - log.debug("Evicting {} from leaderboard. It is no longer active leader.", leadership);
582 - onLeadershipEvent(new LeadershipEvent(LeadershipEvent.Type.LEADER_BOOTED, leadership));
583 - });
584 -
585 - // add missing leaders
586 - diff.entriesOnlyOnRight().forEach((path, leadership) -> {
587 - log.debug("Adding {} to leaderboard. It is now the active leader.", leadership);
588 - onLeadershipEvent(new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, leadership));
589 - });
590 -
591 - // add updated leaders
592 - diff.entriesDiffering().forEach((path, difference) -> {
593 - Leadership current = difference.leftValue();
594 - Leadership updated = difference.rightValue();
595 - if (current.epoch() < updated.epoch()) {
596 - log.debug("Updated {} in leaderboard.", updated);
597 - onLeadershipEvent(new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, updated));
598 - }
599 - });
600 - } catch (Exception e) {
601 - log.debug("Failed to refresh leader board", e);
602 - }
603 - }
604 -
605 - private class InternalClusterEventListener implements ClusterEventListener {
606 -
607 - @Override
608 - public void event(ClusterEvent event) {
609 - if (event.type() == Type.INSTANCE_DEACTIVATED || event.type() == Type.INSTANCE_REMOVED) {
610 - scheduleStaleLeadershipPurge(0);
611 - }
612 - }
613 - }
614 -}